3b41c700e2b1a6d89ffd8521002f58fd6e8753b2
[debian/amanda] / server-src / amcheck.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-2000 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: amcheck.c,v 1.149 2006/08/24 01:57:16 paddy_s Exp $
28  *
29  * checks for common problems in server and clients
30  */
31 #include "amanda.h"
32 #include "util.h"
33 #include "conffile.h"
34 #include "columnar.h"
35 #include "fsusage.h"
36 #include "diskfile.h"
37 #include "tapefile.h"
38 #include "packet.h"
39 #include "security.h"
40 #include "protocol.h"
41 #include "clock.h"
42 #include "amindex.h"
43 #include "server_util.h"
44 #include "pipespawn.h"
45 #include "amfeatures.h"
46 #include "device.h"
47 #include "property.h"
48 #include "timestamp.h"
49 #include "amxml.h"
50 #include "physmem.h"
51
52 #define BUFFER_SIZE     32768
53
54 static time_t conf_ctimeout;
55 static int overwrite;
56
57 static disklist_t origq;
58
59 static uid_t uid_dumpuser;
60
61 /* local functions */
62
63 void usage(void);
64 pid_t start_client_checks(int fd);
65 pid_t start_server_check(int fd, int do_localchk, int do_tapechk);
66 int main(int argc, char **argv);
67 int check_tapefile(FILE *outf, char *tapefile);
68 int test_server_pgm(FILE *outf, char *dir, char *pgm, int suid, uid_t dumpuid);
69
70 void
71 usage(void)
72 {
73     g_printf(_("Usage: amcheck [-am] [-w] [-sclt] [-M <address>] [-o configoption]* <conf> [host [disk]* ]*\n"));
74     exit(1);
75     /*NOTREACHED*/
76 }
77
78 static am_feature_t *our_features = NULL;
79 static char *our_feature_string = NULL;
80 static char *displayunit;
81 static long int unitdivisor;
82
83 int
84 main(
85     int         argc,
86     char **     argv)
87 {
88     char buffer[BUFFER_SIZE];
89     char *version_string;
90     char *mainfname = NULL;
91     char pid_str[NUM_STR_SIZE];
92     int do_clientchk, client_probs;
93     int do_localchk, do_tapechk, server_probs;
94     pid_t clientchk_pid, serverchk_pid;
95     int opt, tempfd, mainfd;
96     size_t size;
97     amwait_t retstat;
98     pid_t pid;
99     extern int optind;
100     char *mailto = NULL;
101     extern char *optarg;
102     int mailout;
103     int alwaysmail;
104     char *tempfname = NULL;
105     char *conf_diskfile;
106     char *dumpuser;
107     struct passwd *pw;
108     uid_t uid_me;
109     char *errstr;
110     config_overrides_t *cfg_ovr;
111     char *mailer;
112
113     /*
114      * Configure program for internationalization:
115      *   1) Only set the message locale for now.
116      *   2) Set textdomain for all amanda related programs to "amanda"
117      *      We don't want to be forced to support dozens of message catalogs.
118      */  
119     setlocale(LC_MESSAGES, "C");
120     textdomain("amanda"); 
121
122     safe_fd(-1, 0);
123     safe_cd();
124
125     set_pname("amcheck");
126     /* drop root privileges */
127     if (!set_root_privs(0)) {
128         error(_("amcheck must be run setuid root"));
129     }
130
131     /* Don't die when child closes pipe */
132     signal(SIGPIPE, SIG_IGN);
133
134     dbopen(DBG_SUBDIR_SERVER);
135
136     memset(buffer, 0, sizeof(buffer));
137
138     g_snprintf(pid_str, SIZEOF(pid_str), "%ld", (long)getpid());
139
140     add_amanda_log_handler(amanda_log_stderr);
141
142     our_features = am_init_feature_set();
143     our_feature_string = am_feature_to_string(our_features);
144
145     uid_me = geteuid();
146
147     alwaysmail = mailout = overwrite = 0;
148     do_localchk = do_tapechk = do_clientchk = 0;
149     server_probs = client_probs = 0;
150     tempfd = mainfd = -1;
151
152     /* process arguments */
153
154     cfg_ovr = new_config_overrides(argc/2);
155     while((opt = getopt(argc, argv, "M:mawsclto:")) != EOF) {
156         switch(opt) {
157         case 'M':       if (mailto) {
158                             g_printf(_("Multiple -M options\n"));
159                             exit(1);
160                         }
161                         mailto=stralloc(optarg);
162                         if(!validate_mailto(mailto)){
163                            g_printf(_("Invalid characters in mail address\n"));
164                            exit(1);
165                         }
166                         /*FALLTHROUGH*/
167         case 'm':       
168                         mailout = 1;
169                         break;
170         case 'a':       
171                         mailout = 1;
172                         alwaysmail = 1;
173                         break;
174         case 's':       do_localchk = do_tapechk = 1;
175                         break;
176         case 'c':       do_clientchk = 1;
177                         break;
178         case 'l':       do_localchk = 1;
179                         break;
180         case 'w':       overwrite = 1;
181                         break;
182         case 'o':       add_config_override_opt(cfg_ovr, optarg);
183                         break;
184         case 't':       do_tapechk = 1;
185                         break;
186         case '?':
187         default:
188             usage();
189         }
190     }
191     argc -= optind, argv += optind;
192     if(argc < 1) usage();
193
194
195     if ((do_localchk | do_clientchk | do_tapechk) == 0) {
196         /* Check everything if individual checks were not asked for */
197         do_localchk = do_clientchk = do_tapechk = 1;
198     }
199
200     if(overwrite)
201         do_tapechk = 1;
202
203     set_config_overrides(cfg_ovr);
204     config_init(CONFIG_INIT_EXPLICIT_NAME, argv[0]);
205     dbrename(get_config_name(), DBG_SUBDIR_SERVER);
206
207     conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
208     read_diskfile(conf_diskfile, &origq);
209     disable_skip_disk(&origq);
210     amfree(conf_diskfile);
211
212     if (config_errors(NULL) >= CFGERR_WARNINGS) {
213         config_print_errors();
214         if (config_errors(NULL) >= CFGERR_ERRORS) {
215             g_critical(_("errors processing config file"));
216         }
217     }
218
219     mailer = getconf_str(CNF_MAILER);
220     if ((!mailer || *mailer == '\0') && mailout == 1) {
221         if (alwaysmail == 1) {
222             g_printf(_("You can't use -a because a mailer is not defined\n"));
223         } else {
224             g_printf(_("You can't use -m because a mailer is not defined\n"));
225         }
226         exit(1);
227     }
228     if(mailout && !mailto && 
229        (getconf_seen(CNF_MAILTO)==0 || strlen(getconf_str(CNF_MAILTO)) == 0)) {
230         g_printf(_("\nWARNING:No mail address configured in  amanda.conf.\n"));
231         g_printf(_("To receive dump results by email configure the "
232                  "\"mailto\" parameter in amanda.conf\n"));
233         if(alwaysmail)        
234                 g_printf(_("When using -a option please specify -Maddress also\n\n")); 
235         else
236                 g_printf(_("Use -Maddress instead of -m\n\n")); 
237         exit(1);
238     }
239     if(mailout && !mailto)
240     { 
241        if(getconf_seen(CNF_MAILTO) && 
242           strlen(getconf_str(CNF_MAILTO)) > 0) {
243           if(!validate_mailto(getconf_str(CNF_MAILTO))){
244                 g_printf(_("\nMail address in amanda.conf has invalid characters")); 
245                 g_printf(_("\nNo email will be sent\n")); 
246                 mailout = 0;
247           }
248        }
249        else {
250           g_printf(_("\nNo mail address configured in  amanda.conf\n"));
251           if(alwaysmail)        
252                 g_printf(_("When using -a option please specify -Maddress also\n\n")); 
253           else
254                 g_printf(_("Use -Maddress instead of -m\n\n")); 
255           exit(1);
256       }
257     }
258
259     conf_ctimeout = (time_t)getconf_int(CNF_CTIMEOUT);
260
261     errstr = match_disklist(&origq, argc-1, argv+1);
262     if (errstr) {
263         g_printf(_("%s"),errstr);
264         amfree(errstr);
265     }
266
267     /*
268      * Make sure we are running as the dump user.  Don't use
269      * check_running_as(..) here, because we want to produce more
270      * verbose error messages.
271      */
272     dumpuser = getconf_str(CNF_DUMPUSER);
273     if ((pw = getpwnam(dumpuser)) == NULL) {
274         error(_("amanda.conf has dump user configured to \"%s\", but that user does not exist."), dumpuser);
275         /*NOTREACHED*/
276     }
277     uid_dumpuser = pw->pw_uid;
278     if ((pw = getpwuid(uid_me)) == NULL) {
279         error(_("cannot get username for running user, uid %ld is not in your user database."),
280             (long)uid_me);
281         /*NOTREACHED*/
282     }
283 #ifdef CHECK_USERID
284     if (uid_me != uid_dumpuser) {
285         error(_("running as user \"%s\" instead of \"%s\".\n"
286                 "Change user to \"%s\" or change dump user to \"%s\" in amanda.conf"),
287               pw->pw_name, dumpuser, dumpuser, pw->pw_name);
288         /*NOTREACHED*/
289     }
290 #endif
291
292     displayunit = getconf_str(CNF_DISPLAYUNIT);
293     unitdivisor = getconf_unit_divisor();
294
295     /*
296      * If both server and client side checks are being done, the server
297      * check output goes to the main output, while the client check output
298      * goes to a temporary file and is copied to the main output when done.
299      *
300      * If the output is to be mailed, the main output is also a disk file,
301      * otherwise it is stdout.
302      */
303     if(do_clientchk && (do_localchk || do_tapechk)) {
304         /* we need the temp file */
305         tempfname = vstralloc(AMANDA_TMPDIR, "/amcheck.temp.", pid_str, NULL);
306         if((tempfd = open(tempfname, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1) {
307             error(_("could not open temporary amcheck output file %s: %s. Check permissions"), tempfname, strerror(errno));
308             /*NOTREACHED*/
309         }
310         unlink(tempfname);                      /* so it goes away on close */
311         amfree(tempfname);
312     }
313
314     if(mailout) {
315         /* the main fd is a file too */
316         mainfname = vstralloc(AMANDA_TMPDIR, "/amcheck.main.", pid_str, NULL);
317         if((mainfd = open(mainfname, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1) {
318             error(_("could not open amcheck server output file %s: %s. Check permissions"), mainfname, strerror(errno));
319             /*NOTREACHED*/
320         }
321         unlink(mainfname);                      /* so it goes away on close */
322         amfree(mainfname);
323     }
324     else
325         /* just use stdout */
326         mainfd = 1;
327
328     /* start server side checks */
329
330     if(do_localchk || do_tapechk)
331         serverchk_pid = start_server_check(mainfd, do_localchk, do_tapechk);
332     else
333         serverchk_pid = 0;
334
335     /* start client side checks */
336
337     if(do_clientchk) {
338         clientchk_pid = start_client_checks((do_localchk || do_tapechk) ? tempfd : mainfd);
339     } else {
340         clientchk_pid = 0;
341     }
342
343     /* wait for child processes and note any problems */
344
345     while(1) {
346         if((pid = wait(&retstat)) == -1) {
347             if(errno == EINTR) continue;
348             else break;
349         } else if(pid == clientchk_pid) {
350             client_probs = WIFSIGNALED(retstat) || WEXITSTATUS(retstat);
351             clientchk_pid = 0;
352         } else if(pid == serverchk_pid) {
353             server_probs = WIFSIGNALED(retstat) || WEXITSTATUS(retstat);
354             serverchk_pid = 0;
355         } else {
356             char *wait_msg = NULL;
357
358             wait_msg = vstrallocf(_("parent: reaped bogus pid %ld\n"), (long)pid);
359             if (full_write(mainfd, wait_msg, strlen(wait_msg)) < strlen(wait_msg)) {
360                 error(_("write main file: %s"), strerror(errno));
361                 /*NOTREACHED*/
362             }
363             amfree(wait_msg);
364         }
365     }
366
367     /* copy temp output to main output and write tagline */
368
369     if(do_clientchk && (do_localchk || do_tapechk)) {
370         if(lseek(tempfd, (off_t)0, 0) == (off_t)-1) {
371             error(_("seek temp file: %s"), strerror(errno));
372             /*NOTREACHED*/
373         }
374
375         while((size = full_read(tempfd, buffer, SIZEOF(buffer))) > 0) {
376             if (full_write(mainfd, buffer, size) < size) {
377                 error(_("write main file: %s"), strerror(errno));
378                 /*NOTREACHED*/
379             }
380         }
381         if(errno != 0) {
382             error(_("read temp file: %s"), strerror(errno));
383             /*NOTREACHED*/
384         }
385         aclose(tempfd);
386     }
387
388     version_string = vstrallocf(_("\n(brought to you by Amanda %s)\n"), VERSION);
389     if (full_write(mainfd, version_string, strlen(version_string)) < strlen(version_string)) {
390         error(_("write main file: %s"), strerror(errno));
391         /*NOTREACHED*/
392     }
393     amfree(version_string);
394     amfree(our_feature_string);
395     am_release_feature_set(our_features);
396     our_features = NULL;
397
398     /* send mail if requested, but only if there were problems */
399
400     if((server_probs || client_probs || alwaysmail) && mailout) {
401         int mailfd;
402         int nullfd;
403         int errfd;
404         FILE *ferr;
405         char *subject;
406         char **a, **b;
407         GPtrArray *pipeargs;
408         amwait_t retstat;
409         size_t r;
410         size_t w;
411         char *err = NULL;
412         char *extra_info = NULL;
413         char *line = NULL;
414         int rc;
415
416         fflush(stdout);
417         if(lseek(mainfd, (off_t)0, SEEK_SET) == (off_t)-1) {
418             error(_("lseek main file: %s"), strerror(errno));
419             /*NOTREACHED*/
420         }
421         if(alwaysmail && !(server_probs || client_probs)) {
422             subject = vstrallocf(_("%s AMCHECK REPORT: NO PROBLEMS FOUND"),
423                         getconf_str(CNF_ORG));
424         } else {
425             subject = vstrallocf(
426                         _("%s AMANDA PROBLEM: FIX BEFORE RUN, IF POSSIBLE"),
427                         getconf_str(CNF_ORG));
428         }
429         if(mailto) {
430             a = (char **) g_new0(char *, 2);
431             a[1] = stralloc(mailto);
432             a[2] = NULL;
433         } else {
434             /* (note that validate_mailto doesn't allow any quotes, so this
435              * is really just splitting regular old strings) */
436             a = split_quoted_strings(getconf_str(CNF_MAILTO));
437         }
438         if((nullfd = open("/dev/null", O_RDWR)) < 0) {
439             error("nullfd: /dev/null: %s", strerror(errno));
440             /*NOTREACHED*/
441         }
442
443         /* assemble the command line for the mailer */
444         pipeargs = g_ptr_array_sized_new(4);
445         g_ptr_array_add(pipeargs, mailer);
446         g_ptr_array_add(pipeargs, "-s");
447         g_ptr_array_add(pipeargs, subject);
448         for (b = a; *b; b++)
449             g_ptr_array_add(pipeargs, *b);
450         g_ptr_array_add(pipeargs, NULL);
451
452         pipespawnv(mailer, STDIN_PIPE | STDERR_PIPE, 0,
453                    &mailfd, &nullfd, &errfd,
454                    (char **)pipeargs->pdata);
455
456         g_ptr_array_free(pipeargs, FALSE);
457         amfree(subject);
458         amfree(mailto);
459         g_strfreev(a);
460
461         /*
462          * There is the potential for a deadlock here since we are writing
463          * to the process and then reading stderr, but in the normal case,
464          * nothing should be coming back to us, and hopefully in error
465          * cases, the pipe will break and we will exit out of the loop.
466          */
467         signal(SIGPIPE, SIG_IGN);
468         while((r = full_read(mainfd, buffer, SIZEOF(buffer))) > 0) {
469             if((w = full_write(mailfd, buffer, r)) != r) {
470                 if(errno == EPIPE) {
471                     strappend(extra_info, _("EPIPE writing to mail process\n"));
472                     break;
473                 } else if(errno != 0) {
474                     error(_("mailfd write: %s"), strerror(errno));
475                     /*NOTREACHED*/
476                 } else {
477                     error(_("mailfd write: wrote %zd instead of %zd"), w, r);
478                     /*NOTREACHED*/
479                 }
480             }
481         }
482         aclose(mailfd);
483         ferr = fdopen(errfd, "r");
484         if (!ferr) {
485             error(_("Can't fdopen: %s"), strerror(errno));
486             /*NOTREACHED*/
487         }
488         for(; (line = agets(ferr)) != NULL; free(line)) {
489             if (line[0] == '\0')
490                 continue;
491             strappend(extra_info, line);
492             strappend(extra_info, "\n");
493         }
494         afclose(ferr);
495         errfd = -1;
496         rc = 0;
497         while (wait(&retstat) != -1) {
498             if (!WIFEXITED(retstat) || WEXITSTATUS(retstat) != 0) {
499                 char *mailer_error = str_exit_status("mailer", retstat);
500                 strappend(err, mailer_error);
501                 amfree(mailer_error);
502
503                 rc = 1;
504             }
505         }
506         if (rc != 0) {
507             if(extra_info) {
508                 fputs(extra_info, stderr);
509                 amfree(extra_info);
510             }
511             error(_("error running mailer %s: %s"), mailer, err?err:"(unknown)");
512             /*NOTREACHED*/
513         }
514     }
515
516     dbclose();
517     return (server_probs || client_probs);
518 }
519
520 /* --------------------------------------------------- */
521
522 static char *datestamp;
523 static FILE *errf = NULL;
524
525 int check_tapefile(
526     FILE *outf,
527     char *tapefile)
528 {
529     struct stat statbuf;
530     char *quoted;
531     int tapebad = 0;
532
533     if (stat(tapefile, &statbuf) == 0) {
534         if (!S_ISREG(statbuf.st_mode)) {
535             quoted = quote_string(tapefile);
536             g_fprintf(outf, _("ERROR: tapelist %s: should be a regular file.\n"),
537                     quoted);
538             tapebad = 1;
539             amfree(quoted);
540         } else if (access(tapefile, F_OK) != 0) {
541             quoted = quote_string(tapefile);
542             g_fprintf(outf, _("ERROR: can't access tapelist %s\n"), quoted);
543             tapebad = 1;
544             amfree(quoted);
545         } else if (access(tapefile, W_OK) != 0) {
546             quoted = quote_string(tapefile);
547             g_fprintf(outf, _("ERROR: tapelist %s: not writable\n"), quoted);
548             tapebad = 1;
549             amfree(quoted);
550         }
551     }
552     return tapebad;
553 }
554
555 int
556 test_server_pgm(
557     FILE *      outf,
558     char *      dir,
559     char *      pgm,
560     int         suid,
561     uid_t       dumpuid)
562 {
563     struct stat statbuf;
564     int pgmbad = 0;
565     char *quoted;
566
567     pgm = vstralloc(dir, "/", pgm, NULL);
568     quoted = quote_string(pgm);
569     if(stat(pgm, &statbuf) == -1) {
570         g_fprintf(outf, _("ERROR: program %s: does not exist\n"),
571                 quoted);
572         pgmbad = 1;
573     } else if (!S_ISREG(statbuf.st_mode)) {
574         g_fprintf(outf, _("ERROR: program %s: not a file\n"),
575                 quoted);
576         pgmbad = 1;
577     } else if (access(pgm, X_OK) == -1) {
578         g_fprintf(outf, _("ERROR: program %s: not executable\n"),
579                 quoted);
580         pgmbad = 1;
581 #ifndef SINGLE_USERID
582     } else if (suid \
583                && dumpuid != 0
584                && (statbuf.st_uid != 0 || (statbuf.st_mode & 04000) == 0)) {
585         g_fprintf(outf, _("ERROR: program %s: not setuid-root\n"),
586                 quoted);
587         pgmbad = 1;
588 #else
589     /* Quiet unused parameter warnings */
590     (void)suid;
591     (void)dumpuid;
592 #endif /* SINGLE_USERID */
593     }
594     amfree(quoted);
595     amfree(pgm);
596     return pgmbad;
597 }
598
599 /* check that the tape is a valid amanda tape
600    Returns TRUE if all tests passed; FALSE otherwise. */
601 static gboolean test_tape_status(FILE * outf) {
602     int outfd;
603     int nullfd = -1;
604     pid_t devpid;
605     char *amcheck_device = NULL;
606     gchar **args;
607     amwait_t wait_status;
608     gboolean success;
609
610     if ((nullfd = open("/dev/null", O_RDWR)) == -1) {
611         return FALSE;
612     }
613
614     fflush(outf);
615     outfd = fileno(outf);
616
617     amcheck_device = vstralloc(amlibexecdir, "/", "amcheck-device", NULL);
618     args = get_config_options(overwrite? 3 : 2);
619     args[0] = amcheck_device; /* steal the reference */
620     args[1] = g_strdup(get_config_name());
621     if (overwrite)
622         args[2] = g_strdup("-w");
623
624     /* run libexecdir/amcheck-device.pl, capturing STDERR and STDOUT to outf */
625     devpid = pipespawnv(amcheck_device, 0, 0,
626             &nullfd, &outfd, &outfd,
627             (char **)args);
628
629     /* and immediately wait for it to die */
630     waitpid(devpid, &wait_status, 0);
631
632     if (WIFSIGNALED(wait_status)) {
633         g_fprintf(outf, _("amcheck-device terminated with signal %d"),
634                   WTERMSIG(wait_status));
635         success = FALSE;
636     } else if (WIFEXITED(wait_status)) {
637         success = (WEXITSTATUS(wait_status) == 0);
638     } else {
639         success = FALSE;
640     }
641
642     g_strfreev(args);
643     close(nullfd);
644
645     return success;
646 }
647
648 pid_t
649 start_server_check(
650     int         fd,
651     int         do_localchk,
652     int         do_tapechk)
653 {
654     struct fs_usage fsusage;
655     FILE *outf = NULL;
656     pid_t pid G_GNUC_UNUSED;
657     int confbad = 0, tapebad = 0, disklow = 0, logbad = 0;
658     int userbad = 0, infobad = 0, indexbad = 0, pgmbad = 0;
659     int testtape = do_tapechk;
660     tapetype_t *tp = NULL;
661     char *quoted;
662     int res;
663     intmax_t kb_avail;
664     off_t tape_size;
665
666     switch(pid = fork()) {
667     case -1:
668         error(_("could not spawn a process for checking the server: %s"), strerror(errno));
669         g_assert_not_reached();
670         
671     case 0:
672         break;
673         
674     default:
675         return pid;
676     }
677     
678     dup2(fd, 1);
679     dup2(fd, 2);
680     
681     set_pname("amcheck-server");
682     
683     startclock();
684
685     if((outf = fdopen(fd, "w")) == NULL) {
686         error(_("fdopen %d: %s"), fd, strerror(errno));
687         /*NOTREACHED*/
688     }
689     errf = outf;
690
691     g_fprintf(outf, _("Amanda Tape Server Host Check\n"));
692     g_fprintf(outf, "-----------------------------\n");
693
694     if (do_localchk || testtape) {
695         tp = lookup_tapetype(getconf_str(CNF_TAPETYPE));
696     }
697
698     /*
699      * Check various server side config file settings.
700      */
701     if(do_localchk) {
702         char *ColumnSpec;
703         char *errstr = NULL;
704         char *lbl_templ;
705
706         ColumnSpec = getconf_str(CNF_COLUMNSPEC);
707         if(SetColumnDataFromString(ColumnData, ColumnSpec, &errstr) < 0) {
708             g_fprintf(outf, _("ERROR: %s\n"), errstr);
709             amfree(errstr);
710             confbad = 1;
711         }
712         lbl_templ = tapetype_get_lbl_templ(tp);
713         if(strcmp(lbl_templ, "") != 0) {
714             lbl_templ = config_dir_relative(lbl_templ);
715             if(access(lbl_templ, R_OK) == -1) {
716                 g_fprintf(outf,
717                         _("ERROR: cannot read label template (lbl-templ) file %s: %s. Check permissions\n"),
718                         lbl_templ,
719                         strerror(errno));
720                 confbad = 1;
721             }
722 #if !defined(HAVE_LPR_CMD)
723             g_fprintf(outf, _("ERROR: lbl-templ set but no LPR command defined. You should reconfigure amanda\n       and make sure it finds a lpr or lp command.\n"));
724             confbad = 1;
725 #endif
726         }
727
728         if (getconf_int(CNF_FLUSH_THRESHOLD_SCHEDULED) <
729                                  getconf_int(CNF_FLUSH_THRESHOLD_DUMPED)) {
730             g_fprintf(outf, _("WARNING: flush_threshold_dumped (%d) must be less than or equal to flush_threshold_scheduled (%d).\n"), 
731                       getconf_int(CNF_FLUSH_THRESHOLD_DUMPED),
732                       getconf_int(CNF_FLUSH_THRESHOLD_SCHEDULED));
733         }
734
735         if (getconf_int(CNF_FLUSH_THRESHOLD_SCHEDULED) <
736                                  getconf_int(CNF_TAPERFLUSH)) {
737             g_fprintf(outf, _("WARNING: taperflush (%d) must be less than or equal to flush_threshold_scheduled (%d).\n"), 
738                       getconf_int(CNF_TAPERFLUSH),
739                       getconf_int(CNF_FLUSH_THRESHOLD_SCHEDULED));
740         }
741
742         if (getconf_int(CNF_TAPERFLUSH) > 0 &&
743             !getconf_boolean(CNF_AUTOFLUSH)) {
744             g_fprintf(outf, _("WARNING: autoflush must be set to 'yes' if taperflush (%d) is greater that 0.\n"),
745                       getconf_int(CNF_TAPERFLUSH));
746         }
747
748         /* Double-check that 'localhost' resolves properly */
749         if ((res = resolve_hostname("localhost", 0, NULL, NULL) != 0)) {
750             g_fprintf(outf, _("ERROR: Cannot resolve `localhost': %s\n"), gai_strerror(res));
751             confbad = 1;
752         }
753
754         if (!getconf_seen(CNF_TAPETYPE)) {
755             g_fprintf(outf,
756                       _("ERROR: no tapetype specified; you must give a value for "
757                         "the 'tapetype' parameter\n"));
758             confbad = 1;
759         }
760
761     }
762
763     /*
764      * Look up the programs used on the server side.
765      */
766     if(do_localchk) {
767         /* 
768          * entreprise version will do planner/dumper suid check
769          */
770         if(access(amlibexecdir, X_OK) == -1) {
771             quoted = quote_string(amlibexecdir);
772             g_fprintf(outf, _("ERROR: Directory %s containing Amanda tools is not accessible\n."),
773                     quoted);
774             g_fprintf(outf, _("Check permissions\n"));
775             pgmbad = 1;
776             amfree(quoted);
777         } else {
778             if(test_server_pgm(outf, amlibexecdir, "planner", 1, uid_dumpuser))
779                 pgmbad = 1;
780             if(test_server_pgm(outf, amlibexecdir, "dumper", 1, uid_dumpuser))
781                 pgmbad = 1;
782             if(test_server_pgm(outf, amlibexecdir, "driver", 0, uid_dumpuser))
783                 pgmbad = 1;
784             if(test_server_pgm(outf, amlibexecdir, "taper", 0, uid_dumpuser))
785                 pgmbad = 1;
786             if(test_server_pgm(outf, amlibexecdir, "amtrmidx", 0, uid_dumpuser))
787                 pgmbad = 1;
788             if(test_server_pgm(outf, amlibexecdir, "amlogroll", 0, uid_dumpuser))
789                 pgmbad = 1;
790         }
791         if(access(sbindir, X_OK) == -1) {
792             quoted = quote_string(sbindir);
793             g_fprintf(outf, _("ERROR: Directory %s containing Amanda tools is not accessible\n"),
794                     sbindir);
795             g_fprintf(outf, _("Check permissions\n"));
796             pgmbad = 1;
797             amfree(quoted);
798         } else {
799             if(test_server_pgm(outf, sbindir, "amgetconf", 0, uid_dumpuser))
800                 pgmbad = 1;
801             if(test_server_pgm(outf, sbindir, "amcheck", 1, uid_dumpuser))
802                 pgmbad = 1;
803             if(test_server_pgm(outf, sbindir, "amdump", 0, uid_dumpuser))
804                 pgmbad = 1;
805             if(test_server_pgm(outf, sbindir, "amreport", 0, uid_dumpuser))
806                 pgmbad = 1;
807         }
808         if(access(COMPRESS_PATH, X_OK) == -1) {
809             quoted = quote_string(COMPRESS_PATH);
810             g_fprintf(outf, _("WARNING: %s is not executable, server-compression "
811                             "and indexing will not work. \n"),quoted);
812             g_fprintf(outf, _("Check permissions\n"));
813             amfree(quoted);
814         }
815     }
816
817     /*
818      * Check that the directory for the tapelist file is writable, as well
819      * as the tapelist file itself (if it already exists).  Also, check for
820      * a "hold" file (just because it is convenient to do it here) and warn
821      * if tapedev is set to the null device.
822      */
823
824     if(do_localchk || do_tapechk) {
825         char *tapefile;
826         char *newtapefile;
827         char *tape_dir;
828         char *lastslash;
829         char *holdfile;
830         char * tapename;
831         struct stat statbuf;
832         
833         tapefile = config_dir_relative(getconf_str(CNF_TAPELIST));
834         /*
835          * XXX There Really Ought to be some error-checking here... dhw
836          */
837         tape_dir = stralloc(tapefile);
838         if ((lastslash = strrchr((const char *)tape_dir, '/')) != NULL) {
839             *lastslash = '\0';
840         /*
841          * else whine Really Loudly about a path with no slashes??!?
842          */
843         }
844         if(access(tape_dir, W_OK) == -1) {
845             quoted = quote_string(tape_dir);
846             g_fprintf(outf, _("ERROR: tapelist dir %s: not writable.\nCheck permissions\n"), 
847                     quoted);
848             tapebad = 1;
849             amfree(quoted);
850         }
851         else if(stat(tapefile, &statbuf) == -1) {
852             if (errno != ENOENT) {
853                 quoted = quote_string(tape_dir);
854                 g_fprintf(outf, _("ERROR: tapelist %s (%s), "
855                         "you must create an empty file.\n"),
856                         quoted, strerror(errno));
857                 tapebad = 1;
858                 amfree(quoted);
859             } else {
860                 g_fprintf(outf, _("NOTE: tapelist will be created on the next run.\n"));
861             }
862         } else {
863             tapebad |= check_tapefile(outf, tapefile);
864             if (tapebad == 0 && read_tapelist(tapefile)) {
865                 quoted = quote_string(tapefile);
866                 g_fprintf(outf, _("ERROR: tapelist %s: parse error\n"), quoted);
867                 tapebad = 1;
868                 amfree(quoted);
869             }
870             newtapefile = stralloc2(tapefile, ".new");
871             tapebad |= check_tapefile(outf, newtapefile);
872             amfree(newtapefile);
873             newtapefile = stralloc2(tapefile, ".amlabel");
874             tapebad |= check_tapefile(outf, newtapefile);
875             amfree(newtapefile);
876             newtapefile = stralloc2(tapefile, ".amlabel.new");
877             tapebad |= check_tapefile(outf, newtapefile);
878             amfree(newtapefile);
879             newtapefile = stralloc2(tapefile, ".yesterday");
880             tapebad |= check_tapefile(outf, newtapefile);
881             amfree(newtapefile);
882             newtapefile = stralloc2(tapefile, ".yesterday.new");
883             tapebad |= check_tapefile(outf, newtapefile);
884             amfree(newtapefile);
885         }
886         holdfile = config_dir_relative("hold");
887         if(access(holdfile, F_OK) != -1) {
888             quoted = quote_string(holdfile);
889             g_fprintf(outf, _("WARNING: hold file %s exists."), holdfile);
890             g_fprintf(outf, _("Amdump will sleep as long as this file exists.\n"));
891             g_fprintf(outf, _("You might want to delete the existing hold file\n"));
892             amfree(quoted);
893         }
894         amfree(tapefile);
895         amfree(tape_dir);
896         amfree(holdfile);
897         tapename = getconf_str(CNF_TAPEDEV);
898         if (tapename == NULL) {
899             if (getconf_str(CNF_TPCHANGER) == NULL) {
900                 g_fprintf(outf, _("WARNING:Parameter \"tapedev\" or \"tpchanger\" not specified in amanda.conf.\n"));
901                 testtape = 0;
902                 do_tapechk = 0;
903             }
904         }
905     }
906
907     /* check available disk space */
908
909     if(do_localchk) {
910         identlist_t    il;
911         holdingdisk_t *hdp;
912
913         for (il = getconf_identlist(CNF_HOLDINGDISK);
914                 il != NULL;
915                 il = il->next) {
916             hdp = lookup_holdingdisk(il->data);
917             quoted = quote_string(holdingdisk_get_diskdir(hdp));
918             if(get_fs_usage(holdingdisk_get_diskdir(hdp), NULL, &fsusage) == -1) {
919                 g_fprintf(outf, _("ERROR: holding dir %s (%s), "
920                         "you must create a directory.\n"),
921                         quoted, strerror(errno));
922                 disklow = 1;
923                 amfree(quoted);
924                 continue;
925             }
926
927             /* do the division first to avoid potential integer overflow */
928             if (fsusage.fsu_bavail_top_bit_set)
929                 kb_avail = 0;
930             else
931                 kb_avail = fsusage.fsu_bavail / 1024 * fsusage.fsu_blocksize;
932
933             if(access(holdingdisk_get_diskdir(hdp), W_OK) == -1) {
934                 g_fprintf(outf, _("ERROR: holding disk %s: not writable: %s.\n"),
935                         quoted, strerror(errno));
936                 g_fprintf(outf, _("Check permissions\n"));
937                 disklow = 1;
938             }
939             else if(access(holdingdisk_get_diskdir(hdp), X_OK) == -1) {
940                 g_fprintf(outf, _("ERROR: holding disk %s: not searcheable: %s.\n"),
941                         quoted, strerror(errno));
942                 g_fprintf(outf, _("Check permissions of ancestors of %s\n"), quoted);
943                 disklow = 1;
944             }
945             else if(holdingdisk_get_disksize(hdp) > (off_t)0) {
946                 if(kb_avail == 0) {
947                     g_fprintf(outf,
948                             _("WARNING: holding disk %s: "
949                             "no space available (%lld %sB requested)\n"), quoted,
950                             (long long)(holdingdisk_get_disksize(hdp)/(off_t)unitdivisor),
951                             displayunit);
952                     disklow = 1;
953                 }
954                 else if(kb_avail < holdingdisk_get_disksize(hdp)) {
955                     g_fprintf(outf,
956                             _("WARNING: holding disk %s: "
957                             "only %lld %sB available (%lld %sB requested)\n"), quoted,
958                             (long long)(kb_avail / (off_t)unitdivisor),
959                             displayunit,
960                             (long long)(holdingdisk_get_disksize(hdp)/(off_t)unitdivisor),
961                             displayunit);
962                     disklow = 1;
963                 }
964                 else {
965                     g_fprintf(outf,
966                             _("Holding disk %s: %lld %sB disk space available,"
967                             " using %lld %sB as requested\n"),
968                             quoted,
969                             (long long)(kb_avail / (off_t)unitdivisor),
970                             displayunit,
971                             (long long)(holdingdisk_get_disksize(hdp)/(off_t)unitdivisor),
972                             displayunit);
973                 }
974             }
975             else {
976                 if(kb_avail < -holdingdisk_get_disksize(hdp)) {
977                     g_fprintf(outf,
978                             _("WARNING: holding disk %s: "
979                             "only %lld %sB free, using nothing\n"),
980                             quoted, (long long)(kb_avail / (off_t)unitdivisor),
981                             displayunit);
982                     g_fprintf(outf, _("WARNING: Not enough free space specified in amanda.conf\n"));
983                     disklow = 1;
984                 }
985                 else {
986                     g_fprintf(outf,
987                             _("Holding disk %s: %lld %sB disk space available, using %lld %sB\n"),
988                             quoted,
989                             (long long)(kb_avail/(off_t)unitdivisor),
990                             displayunit,
991                             (long long)((kb_avail + holdingdisk_get_disksize(hdp)) / (off_t)unitdivisor),
992                             displayunit);
993                 }
994             }
995             amfree(quoted);
996         }
997     }
998
999     /* check that the log file is writable if it already exists */
1000
1001     if(do_localchk) {
1002         char *conf_logdir;
1003         char *logfile;
1004         char *olddir;
1005         struct stat stat_old;
1006         struct stat statbuf;
1007
1008         conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
1009         logfile = vstralloc(conf_logdir, "/log", NULL);
1010
1011         quoted = quote_string(conf_logdir);
1012         if(stat(conf_logdir, &statbuf) == -1) {
1013             g_fprintf(outf, _("ERROR: logdir %s (%s), you must create directory.\n"),
1014                     quoted, strerror(errno));
1015             disklow = 1;
1016         }
1017         else if(access(conf_logdir, W_OK) == -1) {
1018             g_fprintf(outf, _("ERROR: log dir %s: not writable\n"), quoted);
1019             logbad = 1;
1020         }
1021         amfree(quoted);
1022
1023         if(logbad == 0 && access(logfile, F_OK) == 0) {
1024             testtape = 0;
1025             logbad = 2;
1026             if(access(logfile, W_OK) != 0) {
1027                 quoted = quote_string(logfile);
1028                 g_fprintf(outf, _("ERROR: log file %s: not writable\n"), quoted);
1029                 amfree(quoted);
1030             }
1031         }
1032
1033         olddir = vstralloc(conf_logdir, "/oldlog", NULL);
1034         quoted = quote_string(olddir);
1035         if (logbad == 0 && stat(olddir,&stat_old) == 0) { /* oldlog exist */
1036             if(!(S_ISDIR(stat_old.st_mode))) {
1037                 g_fprintf(outf, _("ERROR: oldlog directory %s is not a directory\n"),
1038                         quoted);
1039                 g_fprintf(outf, _("Remove the entry and create a new directory\n"));
1040                 logbad = 1;
1041             }
1042             if(logbad == 0 && access(olddir, W_OK) == -1) {
1043                 g_fprintf(outf, _("ERROR: oldlog dir %s: not writable\n"), quoted);
1044                 g_fprintf(outf, _("Check permissions\n"));
1045                 logbad = 1;
1046             }
1047         }
1048         else if(logbad == 0 && lstat(olddir,&stat_old) == 0) {
1049             g_fprintf(outf, _("ERROR: oldlog directory %s is not a directory\n"),
1050                     quoted);
1051                 g_fprintf(outf, _("Remove the entry and create a new directory\n"));
1052             logbad = 1;
1053         }
1054         amfree(quoted);
1055
1056         if (logbad == 0 && testtape) {
1057             logfile = newvstralloc(logfile, conf_logdir, "/amdump", NULL);
1058             if (access(logfile, F_OK) == 0) {
1059                 testtape = 0;
1060                 logbad = 2;
1061             }
1062         }
1063
1064         amfree(olddir);
1065         amfree(logfile);
1066         amfree(conf_logdir);
1067     }
1068
1069     if (testtape) {
1070         tapebad = !test_tape_status(outf);
1071     } else if (do_tapechk) {
1072         g_fprintf(outf, _("WARNING: skipping tape test because amdump or amflush seem to be running\n"));
1073         g_fprintf(outf, _("WARNING: if they are not, you must run amcleanup\n"));
1074     } else if (logbad == 2) {
1075         g_fprintf(outf, _("NOTE: amdump or amflush seem to be running\n"));
1076         g_fprintf(outf, _("NOTE: if they are not, you must run amcleanup\n"));
1077
1078         /* we skipped the tape checks, but this is just a NOTE and
1079          * should not result in a nonzero exit status, so reset logbad to 0 */
1080         logbad = 0;
1081     } else {
1082         g_fprintf(outf, _("NOTE: skipping tape checks\n"));
1083     }
1084
1085     /*
1086      * See if the information file and index directory for each client
1087      * and disk is OK.  Since we may be seeing clients and/or disks for
1088      * the first time, these are just warnings, not errors.
1089      */
1090     if(do_localchk) {
1091         char *conf_infofile;
1092         char *conf_indexdir;
1093         char *hostinfodir = NULL;
1094         char *hostindexdir = NULL;
1095         char *diskdir = NULL;
1096         char *infofile = NULL;
1097         struct stat statbuf;
1098         disk_t *dp;
1099         am_host_t *hostp;
1100         int indexdir_checked = 0;
1101         int hostindexdir_checked = 0;
1102         char *host;
1103         char *disk;
1104         int conf_tapecycle, conf_runspercycle;
1105         identlist_t pp_scriptlist;
1106         gboolean printed_small_tape_splitsize_warning = FALSE;
1107         char *small_tape_splitsize_warning =
1108             _(" This may create > 1000 parts, severely degrading backup/restore performance.\n"
1109             " To remedy, increase tape_splitsize or disable splitting by setting it to 0\n"
1110             " See http://wiki.zmanda.com/index.php/Splitsize_too_small for more information.\n");
1111
1112         gboolean printed_small_fallback_splitsize_warning = FALSE;
1113         char *small_fallback_splitsize_warning =
1114             _(" This may create > 1000 parts, severely degrading backup/restore performance.\n"
1115             " To remedy, create/set a split_diskbuffer or increase fallback_splitsize\n"
1116             " See http://wiki.zmanda.com/index.php/Splitsize_too_small for more information.\n");
1117
1118
1119         conf_tapecycle = getconf_int(CNF_TAPECYCLE);
1120         conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
1121
1122         if(conf_tapecycle <= conf_runspercycle) {
1123                 g_fprintf(outf, _("WARNING: tapecycle (%d) <= runspercycle (%d).\n"),
1124                         conf_tapecycle, conf_runspercycle);
1125         }
1126
1127         conf_infofile = config_dir_relative(getconf_str(CNF_INFOFILE));
1128         conf_indexdir = config_dir_relative(getconf_str(CNF_INDEXDIR));
1129
1130         quoted = quote_string(conf_infofile);
1131         if(stat(conf_infofile, &statbuf) == -1) {
1132             if (errno == ENOENT) {
1133                 g_fprintf(outf, _("NOTE: conf info dir %s does not exist\n"),
1134                         quoted);
1135                 g_fprintf(outf, _("NOTE: it will be created on the next run.\n"));
1136             } else {
1137                 g_fprintf(outf, _("ERROR: conf info dir %s (%s)\n"),
1138                         quoted, strerror(errno));
1139                 infobad = 1;
1140             }   
1141             amfree(conf_infofile);
1142         } else if (!S_ISDIR(statbuf.st_mode)) {
1143             g_fprintf(outf, _("ERROR: info dir %s: not a directory\n"), quoted);
1144             g_fprintf(outf, _("Remove the entry and create a new directory\n"));
1145             amfree(conf_infofile);
1146             infobad = 1;
1147         } else if (access(conf_infofile, W_OK) == -1) {
1148             g_fprintf(outf, _("ERROR: info dir %s: not writable\n"), quoted);
1149             g_fprintf(outf, _("Check permissions\n"));
1150             amfree(conf_infofile);
1151             infobad = 1;
1152         } else {
1153             char *errmsg = NULL;
1154             if (check_infofile(conf_infofile, &origq, &errmsg) == -1) {
1155                 g_fprintf(outf, "ERROR: Can't copy infofile: %s\n", errmsg);
1156                 infobad = 1;
1157                 amfree(errmsg);
1158             }
1159             strappend(conf_infofile, "/");
1160         }
1161         amfree(quoted);
1162
1163         while(!empty(origq)) {
1164             hostp = origq.head->host;
1165             host = sanitise_filename(hostp->hostname);
1166             if(conf_infofile) {
1167                 hostinfodir = newstralloc2(hostinfodir, conf_infofile, host);
1168                 quoted = quote_string(hostinfodir);
1169                 if(stat(hostinfodir, &statbuf) == -1) {
1170                     if (errno == ENOENT) {
1171                         g_fprintf(outf, _("NOTE: host info dir %s does not exist\n"),
1172                                 quoted);
1173                         g_fprintf(outf,
1174                                 _("NOTE: it will be created on the next run.\n"));
1175                     } else {
1176                         g_fprintf(outf, _("ERROR: host info dir %s (%s)\n"),
1177                                 quoted, strerror(errno));
1178                         infobad = 1;
1179                     }   
1180                     amfree(hostinfodir);
1181                 } else if (!S_ISDIR(statbuf.st_mode)) {
1182                     g_fprintf(outf, _("ERROR: info dir %s: not a directory\n"),
1183                             quoted);
1184                     g_fprintf(outf, _("Remove the entry and create a new directory\n"));
1185                     amfree(hostinfodir);
1186                     infobad = 1;
1187                 } else if (access(hostinfodir, W_OK) == -1) {
1188                     g_fprintf(outf, _("ERROR: info dir %s: not writable\n"), quoted);
1189                     g_fprintf(outf, _("Check permissions\n"));
1190                     amfree(hostinfodir);
1191                     infobad = 1;
1192                 } else {
1193                     strappend(hostinfodir, "/");
1194                 }
1195                 amfree(quoted);
1196             }
1197             for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1198                 disk = sanitise_filename(dp->name);
1199                 if(hostinfodir) {
1200                     char *quotedif;
1201
1202                     diskdir = newstralloc2(diskdir, hostinfodir, disk);
1203                     infofile = vstralloc(diskdir, "/", "info", NULL);
1204                     quoted = quote_string(diskdir);
1205                     quotedif = quote_string(infofile);
1206                     if(stat(diskdir, &statbuf) == -1) {
1207                         if (errno == ENOENT) {
1208                             g_fprintf(outf, _("NOTE: info dir %s does not exist\n"),
1209                                 quoted);
1210                             g_fprintf(outf,
1211                                 _("NOTE: it will be created on the next run.\n"));
1212                         } else {
1213                             g_fprintf(outf, _("ERROR: info dir %s (%s)\n"),
1214                                     quoted, strerror(errno));
1215                             infobad = 1;
1216                         }       
1217                     } else if (!S_ISDIR(statbuf.st_mode)) {
1218                         g_fprintf(outf, _("ERROR: info dir %s: not a directory\n"),
1219                                 quoted);
1220                         g_fprintf(outf, _("Remove the entry and create a new directory\n"));
1221                         infobad = 1;
1222                     } else if (access(diskdir, W_OK) == -1) {
1223                         g_fprintf(outf, _("ERROR: info dir %s: not writable\n"),
1224                                 quoted);
1225                         g_fprintf(outf,_("Check permissions\n"));
1226                         infobad = 1;
1227                     } else if(stat(infofile, &statbuf) == -1) {
1228                         if (errno == ENOENT) {
1229                             g_fprintf(outf, _("NOTE: info file %s does not exist\n"),
1230                                     quotedif);
1231                             g_fprintf(outf, _("NOTE: it will be created on the next run.\n"));
1232                         } else {
1233                             g_fprintf(outf, _("ERROR: info dir %s (%s)\n"),
1234                                     quoted, strerror(errno));
1235                             infobad = 1;
1236                         }       
1237                     } else if (!S_ISREG(statbuf.st_mode)) {
1238                         g_fprintf(outf, _("ERROR: info file %s: not a file\n"),
1239                                 quotedif);
1240                         g_fprintf(outf, _("Remove the entry and create a new file\n"));
1241                         infobad = 1;
1242                     } else if (access(infofile, R_OK) == -1) {
1243                         g_fprintf(outf, _("ERROR: info file %s: not readable\n"),
1244                                 quotedif);
1245                         infobad = 1;
1246                     }
1247                     amfree(quotedif);
1248                     amfree(quoted);
1249                     amfree(infofile);
1250                 }
1251                 if(dp->index) {
1252                     if(! indexdir_checked) {
1253                         quoted = quote_string(conf_indexdir);
1254                         if(stat(conf_indexdir, &statbuf) == -1) {
1255                             if (errno == ENOENT) {
1256                                 g_fprintf(outf, _("NOTE: index dir %s does not exist\n"),
1257                                         quoted);
1258                                 g_fprintf(outf, _("NOTE: it will be created on the next run.\n"));
1259                             } else {
1260                                 g_fprintf(outf, _("ERROR: index dir %s (%s)\n"),
1261                                         quoted, strerror(errno));
1262                                 indexbad = 1;
1263                             }   
1264                             amfree(conf_indexdir);
1265                         } else if (!S_ISDIR(statbuf.st_mode)) {
1266                             g_fprintf(outf, _("ERROR: index dir %s: not a directory\n"),
1267                                     quoted);
1268                             g_fprintf(outf, _("Remove the entry and create a new directory\n"));
1269                             amfree(conf_indexdir);
1270                             indexbad = 1;
1271                         } else if (access(conf_indexdir, W_OK) == -1) {
1272                             g_fprintf(outf, _("ERROR: index dir %s: not writable\n"),
1273                                     quoted);
1274                             amfree(conf_indexdir);
1275                             indexbad = 1;
1276                         } else {
1277                             strappend(conf_indexdir, "/");
1278                         }
1279                         indexdir_checked = 1;
1280                         amfree(quoted);
1281                     }
1282                     if(conf_indexdir) {
1283                         if(! hostindexdir_checked) {
1284                             hostindexdir = stralloc2(conf_indexdir, host);
1285                             quoted = quote_string(hostindexdir);
1286                             if(stat(hostindexdir, &statbuf) == -1) {
1287                                 if (errno == ENOENT) {
1288                                     g_fprintf(outf, _("NOTE: index dir %s does not exist\n"),
1289                                             quoted);
1290                                     g_fprintf(outf, _("NOTE: it will be created on the next run.\n"));
1291                                 } else {
1292                                     g_fprintf(outf, _("ERROR: index dir %s (%s)\n"),
1293                                             quoted, strerror(errno));
1294                                     indexbad = 1;
1295                                 }
1296                                 amfree(hostindexdir);
1297                             } else if (!S_ISDIR(statbuf.st_mode)) {
1298                                 g_fprintf(outf, _("ERROR: index dir %s: not a directory\n"),
1299                                         quoted);
1300                                 g_fprintf(outf, _("Remove the entry and create a new directory\n"));
1301                                 amfree(hostindexdir);
1302                                 indexbad = 1;
1303                             } else if (access(hostindexdir, W_OK) == -1) {
1304                                 g_fprintf(outf, _("ERROR: index dir %s: not writable\n"),
1305                                         quoted);
1306                                 amfree(hostindexdir);
1307                                 indexbad = 1;
1308                             } else {
1309                                 strappend(hostindexdir, "/");
1310                             }
1311                             hostindexdir_checked = 1;
1312                             amfree(quoted);
1313                         }
1314                         if(hostindexdir) {
1315                             diskdir = newstralloc2(diskdir, hostindexdir, disk);
1316                             quoted = quote_string(diskdir);
1317                             if(stat(diskdir, &statbuf) == -1) {
1318                                 if (errno == ENOENT) {
1319                                     g_fprintf(outf, _("NOTE: index dir %s does not exist\n"),
1320                                             quoted);
1321                                     g_fprintf(outf, _("NOTE: it will be created on the next run.\n"));
1322                                 } else {
1323                                     g_fprintf(outf, _("ERROR: index dir %s (%s)\n"),
1324                                         quoted, strerror(errno));
1325                                     indexbad = 1;
1326                                 }       
1327                             } else if (!S_ISDIR(statbuf.st_mode)) {
1328                                 g_fprintf(outf, _("ERROR: index dir %s: not a directory\n"),
1329                                         quoted);
1330                                 g_fprintf(outf, _("Remove the entry and create a new directory\n"));
1331                                 indexbad = 1;
1332                             } else if (access(diskdir, W_OK) == -1) {
1333                                 g_fprintf(outf, _("ERROR: index dir %s: is not writable\n"),
1334                                         quoted);
1335                                 indexbad = 1;
1336                             }
1337                             amfree(quoted);
1338                         }
1339                     }
1340                 }
1341
1342                 if ( dp->encrypt == ENCRYPT_SERV_CUST ) {
1343                   if ( dp->srv_encrypt[0] == '\0' ) {
1344                     g_fprintf(outf, _("ERROR: server encryption program not specified\n"));
1345                     g_fprintf(outf, _("Specify \"server_custom_encrypt\" in the dumptype\n"));
1346                     pgmbad = 1;
1347                   }
1348                   else if(access(dp->srv_encrypt, X_OK) == -1) {
1349                     g_fprintf(outf, _("ERROR: %s is not executable, server encryption will not work\n"),
1350                             dp->srv_encrypt );
1351                    g_fprintf(outf, _("Check file type\n"));
1352                     pgmbad = 1;
1353                   }
1354                 }
1355                 if ( dp->compress == COMP_SERVER_CUST ) {
1356                   if ( dp->srvcompprog[0] == '\0' ) {
1357                     g_fprintf(outf, _("ERROR: server custom compression program "
1358                                     "not specified\n"));
1359                     g_fprintf(outf, _("Specify \"server_custom_compress\" in "
1360                                     "the dumptype\n"));
1361                     pgmbad = 1;
1362                   }
1363                   else if(access(dp->srvcompprog, X_OK) == -1) {
1364                     quoted = quote_string(dp->srvcompprog);
1365
1366                     g_fprintf(outf, _("ERROR: %s is not executable, server custom "
1367                                     "compression will not work\n"),
1368                             quoted);
1369                     amfree(quoted);
1370                    g_fprintf(outf, _("Check file type\n"));
1371                     pgmbad = 1;
1372                   }
1373                 }
1374
1375                 /* check tape_splitsize */
1376                 tape_size = tapetype_get_length(tp);
1377                 if (dp->tape_splitsize > tape_size) {
1378                     g_fprintf(outf,
1379                               _("ERROR: %s %s: tape_splitsize > tape size\n"),
1380                               hostp->hostname, dp->name);
1381                     pgmbad = 1;
1382                 }
1383                 if (dp->tape_splitsize && dp->fallback_splitsize * 1024 > physmem_total()) {
1384                     g_fprintf(outf,
1385                               _("ERROR: %s %s: fallback_splitsize > total available memory\n"),
1386                               hostp->hostname, dp->name);
1387                     pgmbad = 1;
1388                 }
1389                 if (dp->tape_splitsize && dp->fallback_splitsize > tape_size) {
1390                     g_fprintf(outf,
1391                               _("ERROR: %s %s: fallback_splitsize > tape size\n"),
1392                               hostp->hostname, dp->name);
1393                     pgmbad = 1;
1394                 }
1395
1396                 /* also check for part sizes that are too small */
1397                 if (dp->tape_splitsize && dp->tape_splitsize * 1000 < tape_size) {
1398                     g_fprintf(outf,
1399                               _("WARNING: %s %s: tape_splitsize of %ju %sB < 0.1%% of tape length.\n"),
1400                               hostp->hostname, dp->name,
1401                               (uintmax_t)dp->tape_splitsize/(uintmax_t)unitdivisor,
1402                               displayunit);
1403                     if (!printed_small_tape_splitsize_warning) {
1404                         printed_small_tape_splitsize_warning = TRUE;
1405                         g_fprintf(outf, "%s", small_tape_splitsize_warning);
1406                     }
1407                 }
1408
1409                 /* fallback splitsize will be used if split_diskbuffer is empty or NULL */
1410                 if (dp->tape_splitsize != 0 && dp->fallback_splitsize != 0 &&
1411                         (dp->split_diskbuffer == NULL ||
1412                          dp->split_diskbuffer[0] == '\0') &&
1413                         dp->fallback_splitsize * 1000 < tape_size) {
1414                     g_fprintf(outf,
1415                           _("WARNING: %s %s: fallback_splitsize of %ju %sB < 0.1%% of tape length.\n"),
1416                           hostp->hostname, dp->name,
1417                           (uintmax_t)dp->fallback_splitsize/(uintmax_t)unitdivisor,
1418                           displayunit);
1419                     if (!printed_small_fallback_splitsize_warning) {
1420                         printed_small_fallback_splitsize_warning = TRUE;
1421                         g_fprintf(outf, "%s", small_fallback_splitsize_warning);
1422                     }
1423                 }
1424
1425                 if (dp->data_path == DATA_PATH_DIRECTTCP) {
1426                     if (dp->compress != COMP_NONE) {
1427                         g_fprintf(outf,
1428                                   _("ERROR: %s %s: Can't compress directtcp data-path\n"),
1429                                   hostp->hostname, dp->name);
1430                         pgmbad = 1;
1431                     }
1432                     if (dp->encrypt != ENCRYPT_NONE) {
1433                         g_fprintf(outf,
1434                                   _("ERROR: %s %s: Can't encrypt directtcp data-path\n"),
1435                                   hostp->hostname, dp->name);
1436                         pgmbad = 1;
1437                     }
1438                     if (dp->to_holdingdisk == HOLD_REQUIRED) {
1439                         g_fprintf(outf,
1440                                   _("ERROR: %s %s: Holding disk can't be use for directtcp data-path\n"),
1441                                   hostp->hostname, dp->name);
1442                         pgmbad = 1;
1443                     } else if (dp->to_holdingdisk == HOLD_AUTO) {
1444                         g_fprintf(outf,
1445                                   _("WARNING: %s %s: Holding disk can't be use for directtcp data-path\n"),
1446                                   hostp->hostname, dp->name);
1447                         pgmbad = 1;
1448                     }
1449                 }
1450
1451                 for (pp_scriptlist = dp->pp_scriptlist; pp_scriptlist != NULL;
1452                      pp_scriptlist = pp_scriptlist->next) {
1453                     pp_script_t *pp_script = lookup_pp_script((char *)pp_scriptlist->data);
1454                     g_assert(pp_script != NULL);
1455                     if (pp_script_get_execute_where(pp_script) == ES_CLIENT &&
1456                         pp_script_get_execute_on(pp_script) & EXECUTE_ON_PRE_HOST_BACKUP) {
1457                         g_fprintf(outf,
1458                                   _("ERROR: %s %s: Can't run pre-host-backup script on client\n"),
1459                                   hostp->hostname, dp->name);
1460                     } else if (pp_script_get_execute_where(pp_script) == ES_CLIENT &&
1461                         pp_script_get_execute_on(pp_script) & EXECUTE_ON_POST_HOST_BACKUP) {
1462                         g_fprintf(outf,
1463                                   _("ERROR: %s %s: Can't run post-host-backup script on client\n"),
1464                                   hostp->hostname, dp->name);
1465                     }
1466                 }
1467
1468                 amfree(disk);
1469                 remove_disk(&origq, dp);
1470             }
1471             amfree(host);
1472             amfree(hostindexdir);
1473             hostindexdir_checked = 0;
1474         }
1475         amfree(diskdir);
1476         amfree(hostinfodir);
1477         amfree(conf_infofile);
1478         amfree(conf_indexdir);
1479     }
1480
1481     amfree(datestamp);
1482
1483     g_fprintf(outf, _("Server check took %s seconds\n"), walltime_str(curclock()));
1484
1485     fflush(outf);
1486     g_debug("userbad: %d", userbad);
1487     g_debug("confbad: %d", confbad);
1488     g_debug("tapebad: %d", tapebad);
1489     g_debug("disklow: %d", disklow);
1490     g_debug("logbad: %d", logbad);
1491     g_debug("infobad: %d", infobad);
1492     g_debug("indexbad: %d", indexbad);
1493     g_debug("pgmbad: %d", pgmbad);
1494
1495     exit(userbad \
1496          || confbad \
1497          || tapebad \
1498          || disklow \
1499          || logbad \
1500          || infobad \
1501          || indexbad \
1502          || pgmbad);
1503     /*NOTREACHED*/
1504     return 0;
1505 }
1506
1507 /* --------------------------------------------------- */
1508
1509 int remote_errors;
1510 FILE *outf;
1511
1512 static void handle_result(void *, pkt_t *, security_handle_t *);
1513 void start_host(am_host_t *hostp);
1514
1515 #define HOST_READY                              ((void *)0)     /* must be 0 */
1516 #define HOST_ACTIVE                             ((void *)1)
1517 #define HOST_DONE                               ((void *)2)
1518
1519 #define DISK_READY                              ((void *)0)     /* must be 0 */
1520 #define DISK_ACTIVE                             ((void *)1)
1521 #define DISK_DONE                               ((void *)2)
1522
1523 void
1524 start_host(
1525     am_host_t *hostp)
1526 {
1527     disk_t *dp;
1528     char *req = NULL;
1529     size_t req_len = 0;
1530     int disk_count;
1531     const security_driver_t *secdrv;
1532     char number[NUM_STR_SIZE];
1533     estimate_t estimate;
1534
1535     if(hostp->up != HOST_READY) {
1536         return;
1537     }
1538
1539     /*
1540      * The first time through here we send a "noop" request.  This will
1541      * return the feature list from the client if it supports that.
1542      * If it does not, handle_result() will set the feature list to an
1543      * empty structure.  In either case, we do the disks on the second
1544      * (and subsequent) pass(es).
1545      */
1546     disk_count = 0;
1547     if(hostp->features != NULL) { /* selfcheck service */
1548         int has_features = am_has_feature(hostp->features,
1549                                           fe_req_options_features);
1550         int has_hostname = am_has_feature(hostp->features,
1551                                           fe_req_options_hostname);
1552         int has_maxdumps = am_has_feature(hostp->features,
1553                                           fe_req_options_maxdumps);
1554         int has_config   = am_has_feature(hostp->features,
1555                                           fe_req_options_config);
1556
1557         if(!am_has_feature(hostp->features, fe_selfcheck_req) &&
1558            !am_has_feature(hostp->features, fe_selfcheck_req_device)) {
1559             g_fprintf(outf,
1560                     _("ERROR: Client %s does not support selfcheck REQ packet.\n"),
1561                     hostp->hostname);
1562             g_fprintf(outf, _("Client might be of a very old version\n"));
1563         }
1564         if(!am_has_feature(hostp->features, fe_selfcheck_rep)) {
1565             g_fprintf(outf,
1566                     _("ERROR: Client %s does not support selfcheck REP packet.\n"),
1567                     hostp->hostname);
1568             g_fprintf(outf, _("Client might be of a very old version\n"));
1569         }
1570         if(!am_has_feature(hostp->features, fe_sendsize_req_options) &&
1571            !am_has_feature(hostp->features, fe_sendsize_req_no_options) &&
1572            !am_has_feature(hostp->features, fe_sendsize_req_device)) {
1573             g_fprintf(outf,
1574                     _("ERROR: Client %s does not support sendsize REQ packet.\n"),
1575                     hostp->hostname);
1576             g_fprintf(outf, _("Client might be of a very old version\n"));
1577         }
1578         if(!am_has_feature(hostp->features, fe_sendsize_rep)) {
1579             g_fprintf(outf,
1580                     _("ERROR: Client %s does not support sendsize REP packet.\n"),
1581                     hostp->hostname);
1582             g_fprintf(outf, _("Client might be of a very old version\n"));
1583         }
1584         if(!am_has_feature(hostp->features, fe_sendbackup_req) &&
1585            !am_has_feature(hostp->features, fe_sendbackup_req_device)) {
1586             g_fprintf(outf,
1587                    _("ERROR: Client %s does not support sendbackup REQ packet.\n"),
1588                    hostp->hostname);
1589             g_fprintf(outf, _("Client might be of a very old version\n"));
1590         }
1591         if(!am_has_feature(hostp->features, fe_sendbackup_rep)) {
1592             g_fprintf(outf,
1593                    _("ERROR: Client %s does not support sendbackup REP packet.\n"),
1594                    hostp->hostname);
1595             g_fprintf(outf, _("Client might be of a very old version\n"));
1596         }
1597
1598         g_snprintf(number, SIZEOF(number), "%d", hostp->maxdumps);
1599         req = vstralloc("SERVICE ", "selfcheck", "\n",
1600                         "OPTIONS ",
1601                         has_features ? "features=" : "",
1602                         has_features ? our_feature_string : "",
1603                         has_features ? ";" : "",
1604                         has_maxdumps ? "maxdumps=" : "",
1605                         has_maxdumps ? number : "",
1606                         has_maxdumps ? ";" : "",
1607                         has_hostname ? "hostname=" : "",
1608                         has_hostname ? hostp->hostname : "",
1609                         has_hostname ? ";" : "",
1610                         has_config   ? "config=" : "",
1611                         has_config   ? get_config_name() : "",
1612                         has_config   ? ";" : "",
1613                         "\n",
1614                         NULL);
1615
1616         req_len = strlen(req);
1617         req_len += 128;                         /* room for SECURITY ... */
1618         req_len += 256;                         /* room for non-disk answers */
1619         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1620             char *l;
1621             char *es;
1622             size_t l_len;
1623             char *o = NULL;
1624             char *calcsize;
1625             char *qname, *b64disk;
1626             char *qdevice, *b64device = NULL;
1627             GPtrArray *errarray;
1628             guint      i;
1629
1630             if(dp->up != DISK_READY || dp->todo != 1) {
1631                 continue;
1632             }
1633             qname = quote_string(dp->name);
1634
1635             errarray = validate_optionstr(dp);
1636             if (errarray->len > 0) {
1637                 for (i=0; i < errarray->len; i++) {
1638                     g_fprintf(outf, _("ERROR: %s:%s %s\n"),
1639                               hostp->hostname, qname,
1640                               (char *)g_ptr_array_index(errarray, i));
1641                 }
1642                 g_ptr_array_free(errarray, TRUE);
1643                 amfree(qname);
1644                 remote_errors++;
1645                 continue;
1646             } else  if (am_has_feature(hostp->features, fe_req_xml)) {
1647                 o = xml_optionstr(dp, 0);
1648             } else {
1649                 o = optionstr(dp);
1650             }
1651
1652             b64disk = amxml_format_tag("disk", dp->name);
1653             qdevice = quote_string(dp->device); 
1654             if (dp->device)
1655                 b64device = amxml_format_tag("diskdevice", dp->device);
1656             if ((dp->name && qname[0] == '"') || 
1657                 (dp->device && qdevice[0] == '"')) {
1658                 if(!am_has_feature(hostp->features, fe_interface_quoted_text)) {
1659                     g_fprintf(outf,
1660                             _("WARNING: %s:%s:%s host does not support quoted text\n"),
1661                             hostp->hostname, qname, qdevice);
1662                     g_fprintf(outf, _("You must upgrade amanda on the client to "
1663                                     "specify a quoted text/device in the disklist, "
1664                                     "or don't use quoted text for the device.\n"));
1665                 }
1666             }
1667
1668             if(dp->device) {
1669                 if(!am_has_feature(hostp->features, fe_selfcheck_req_device)) {
1670                     g_fprintf(outf,
1671                      _("ERROR: %s:%s (%s): selfcheck does not support device.\n"),
1672                      hostp->hostname, qname, dp->device);
1673                     g_fprintf(outf, _("You must upgrade amanda on the client to "
1674                                     "specify a diskdevice in the disklist "     
1675                                     "or don't specify a diskdevice in the disklist.\n"));       
1676                 }
1677                 if(!am_has_feature(hostp->features, fe_sendsize_req_device)) {
1678                     g_fprintf(outf,
1679                      _("ERROR: %s:%s (%s): sendsize does not support device.\n"),
1680                      hostp->hostname, qname, dp->device);
1681                     g_fprintf(outf, _("You must upgrade amanda on the client to "
1682                                     "specify a diskdevice in the disklist"      
1683                                     " or don't specify a diskdevice in the disklist.\n"));      
1684                 }
1685                 if(!am_has_feature(hostp->features, fe_sendbackup_req_device)) {
1686                     g_fprintf(outf,
1687                      _("ERROR: %s:%s (%s): sendbackup does not support device.\n"),
1688                      hostp->hostname, qname, dp->device);
1689                     g_fprintf(outf, _("You must upgrade amanda on the client to "
1690                                     "specify a diskdevice in the disklist"      
1691                                     " or don't specify a diskdevice in the disklist.\n"));      
1692                 }
1693
1694                 if (dp->data_path != DATA_PATH_AMANDA &&
1695                     !am_has_feature(hostp->features, fe_xml_data_path)) {
1696                     g_fprintf(outf,
1697                               _("ERROR: Client %s does not support %s data-path\n"),
1698                               hostp->hostname,  data_path_to_string(dp->data_path));
1699                 } else if (dp->data_path == DATA_PATH_DIRECTTCP &&
1700                     !am_has_feature(hostp->features, fe_xml_directtcp_list)) {
1701                     g_fprintf(outf,
1702                               _("ERROR: Client %s does not support directtcp data-path\n"),
1703                               hostp->hostname);
1704                 }
1705             }
1706             if (dp->program &&
1707                 (strcmp(dp->program,"DUMP") == 0 || 
1708                  strcmp(dp->program,"GNUTAR") == 0)) {
1709                 if(strcmp(dp->program, "DUMP") == 0 &&
1710                    !am_has_feature(hostp->features, fe_program_dump)) {
1711                     g_fprintf(outf, _("ERROR: %s:%s does not support DUMP.\n"),
1712                             hostp->hostname, qname);
1713                     g_fprintf(outf, _("You must upgrade amanda on the client to use DUMP "
1714                                     "or you can use another program.\n"));      
1715                 }
1716                 if(strcmp(dp->program, "GNUTAR") == 0 &&
1717                    !am_has_feature(hostp->features, fe_program_gnutar)) {
1718                     g_fprintf(outf, _("ERROR: %s:%s does not support GNUTAR.\n"),
1719                             hostp->hostname, qname);
1720                     g_fprintf(outf, _("You must upgrade amanda on the client to use GNUTAR "
1721                                     "or you can use another program.\n"));      
1722                 }
1723                 estimate = (estimate_t)GPOINTER_TO_INT(dp->estimatelist->data);
1724                 if(estimate == ES_CALCSIZE &&
1725                    !am_has_feature(hostp->features, fe_calcsize_estimate)) {
1726                     g_fprintf(outf, _("ERROR: %s:%s does not support CALCSIZE for "
1727                                     "estimate, using CLIENT.\n"),
1728                             hostp->hostname, qname);
1729                     g_fprintf(outf, _("You must upgrade amanda on the client to use "
1730                                     "CALCSIZE for estimate or don't use CALCSIZE for estimate.\n"));
1731                     estimate = ES_CLIENT;
1732                 }
1733                 if(estimate == ES_CALCSIZE &&
1734                    am_has_feature(hostp->features, fe_selfcheck_calcsize))
1735                     calcsize = "CALCSIZE ";
1736                 else
1737                     calcsize = "";
1738
1739                 if(dp->compress == COMP_CUST &&
1740                    !am_has_feature(hostp->features, fe_options_compress_cust)) {
1741                   g_fprintf(outf,
1742                           _("ERROR: Client %s does not support custom compression.\n"),
1743                           hostp->hostname);
1744                     g_fprintf(outf, _("You must upgrade amanda on the client to "
1745                                     "use custom compression\n"));
1746                     g_fprintf(outf, _("Otherwise you can use the default client "
1747                                     "compression program.\n"));
1748                 }
1749                 if(dp->encrypt == ENCRYPT_CUST ) {
1750                   if ( !am_has_feature(hostp->features, fe_options_encrypt_cust)) {
1751                     g_fprintf(outf,
1752                             _("ERROR: Client %s does not support data encryption.\n"),
1753                             hostp->hostname);
1754                     g_fprintf(outf, _("You must upgrade amanda on the client to use encryption program.\n"));
1755                     remote_errors++;
1756                   } else if ( dp->compress == COMP_SERVER_FAST || 
1757                               dp->compress == COMP_SERVER_BEST ||
1758                               dp->compress == COMP_SERVER_CUST ) {
1759                     g_fprintf(outf,
1760                             _("ERROR: %s: Client encryption with server compression "
1761                               "is not supported. See amanda.conf(5) for detail.\n"), 
1762                             hostp->hostname);
1763                     remote_errors++;
1764                   } 
1765                 }
1766                 if (am_has_feature(hostp->features, fe_req_xml)) {
1767                     l = vstralloc("<dle>\n"
1768                                   "  <program>",
1769                                   dp->program,
1770                                   "</program>\n", NULL);
1771                     es = xml_estimate(dp->estimatelist, hostp->features);
1772                     vstrextend(&l, es, "\n", NULL);
1773                     amfree(es);
1774                     vstrextend(&l, "  ", b64disk, "\n", NULL);
1775                     if (dp->device)
1776                         vstrextend(&l, "  ", b64device, "\n", NULL);
1777                     vstrextend(&l, o, "</dle>\n", NULL);
1778                 } else {
1779                     if (dp->device) {
1780                         l = vstralloc(calcsize,
1781                                       dp->program, " ",
1782                                       qname, " ",
1783                                       qdevice,
1784                                       " 0 OPTIONS |",
1785                                       o,
1786                                       "\n",
1787                                       NULL);
1788                     } else {
1789                         l = vstralloc(calcsize,
1790                                       dp->program, " ",
1791                                       qname,
1792                                       " 0 OPTIONS |",
1793                                       o,
1794                                       "\n",
1795                                       NULL);
1796                     }
1797                 }
1798             } else {
1799                 if (!am_has_feature(hostp->features, fe_program_application_api) ||
1800                     !am_has_feature(hostp->features, fe_req_xml)) {
1801                     g_fprintf(outf, _("ERROR: %s:%s does not support APPLICATION-API.\n"),
1802                             hostp->hostname, qname);
1803                     g_fprintf(outf, _("Dumptype configuration is not GNUTAR or DUMP."
1804                                     " It is case sensitive\n"));
1805                     remote_errors++;
1806                     l = stralloc("");
1807                 } else {
1808                     l = vstralloc("<dle>\n"
1809                                   "  <program>APPLICATION</program>\n", NULL);
1810                     if (dp->application) {
1811                         application_t *application;
1812                         char          *xml_app;
1813
1814                         application = lookup_application(dp->application);
1815                         if (!application) {
1816                             g_fprintf(outf,
1817                               _("ERROR: application '%s' not found.\n"), dp->application);
1818                         } else {
1819                             xml_app = xml_application(dp, application, hostp->features);
1820                             vstrextend(&l, xml_app, NULL);
1821                             amfree(xml_app);
1822                         }
1823                     }
1824                     if (dp->pp_scriptlist) {
1825                         if (!am_has_feature(hostp->features, fe_pp_script)) {
1826                             g_fprintf(outf,
1827                               _("ERROR: %s:%s does not support SCRIPT-API.\n"),
1828                               hostp->hostname, qname);
1829                         }
1830                     }
1831                     es = xml_estimate(dp->estimatelist, hostp->features);
1832                     vstrextend(&l, es, "\n", NULL);
1833                     amfree(es);
1834                     vstrextend(&l, "  ", b64disk, "\n", NULL);
1835                     if (dp->device)
1836                         vstrextend(&l, "  ", b64device, "\n", NULL);
1837                     vstrextend(&l, o, "</dle>\n", NULL);
1838                 }
1839             }
1840             amfree(qname);
1841             amfree(qdevice);
1842             l_len = strlen(l);
1843             amfree(o);
1844
1845             strappend(req, l);
1846             req_len += l_len;
1847             amfree(l);
1848             dp->up = DISK_ACTIVE;
1849             disk_count++;
1850         }
1851     }
1852     else { /* noop service */
1853         req = vstralloc("SERVICE ", "noop", "\n",
1854                         "OPTIONS ",
1855                         "features=", our_feature_string, ";",
1856                         "\n",
1857                         NULL);
1858         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1859             if(dp->up != DISK_READY || dp->todo != 1) {
1860                 continue;
1861             }
1862             disk_count++;
1863         }
1864     }
1865
1866     if(disk_count == 0) {
1867         amfree(req);
1868         hostp->up = HOST_DONE;
1869         return;
1870     }
1871
1872     secdrv = security_getdriver(hostp->disks->auth);
1873     if (secdrv == NULL) {
1874         fprintf(stderr, _("Could not find security driver \"%s\" for host \"%s\". auth for this dle is invalid\n"),
1875               hostp->disks->auth, hostp->hostname);
1876     } else {
1877         protocol_sendreq(hostp->hostname, secdrv, amhost_get_security_conf, 
1878                          req, conf_ctimeout, handle_result, hostp);
1879     }
1880
1881     amfree(req);
1882
1883     hostp->up = HOST_ACTIVE;
1884 }
1885
1886 pid_t
1887 start_client_checks(
1888     int         fd)
1889 {
1890     am_host_t *hostp;
1891     disk_t *dp, *dp1;
1892     int hostcount;
1893     pid_t pid;
1894     int userbad = 0;
1895
1896     switch(pid = fork()) {
1897     case -1:
1898         error(_("INTERNAL ERROR:could not fork client check: %s"), strerror(errno));
1899         /*NOTREACHED*/
1900
1901     case 0:
1902         break;
1903
1904     default:
1905         return pid;
1906     }
1907
1908     dup2(fd, 1);
1909     dup2(fd, 2);
1910
1911     set_pname("amcheck-clients");
1912
1913     startclock();
1914
1915     if((outf = fdopen(fd, "w")) == NULL) {
1916         error(_("fdopen %d: %s"), fd, strerror(errno));
1917         /*NOTREACHED*/
1918     }
1919     errf = outf;
1920
1921     g_fprintf(outf, _("\nAmanda Backup Client Hosts Check\n"));
1922     g_fprintf(outf,   "--------------------------------\n");
1923
1924     protocol_init();
1925
1926     hostcount = remote_errors = 0;
1927
1928     for(dp = origq.head; dp != NULL; dp = dp->next) {
1929         hostp = dp->host;
1930         if(hostp->up == HOST_READY && dp->todo == 1) {
1931             for(dp1 = hostp->disks; dp1 != NULL; dp1 = dp1->hostnext) {
1932                 run_server_scripts(EXECUTE_ON_PRE_HOST_AMCHECK,
1933                                    get_config_name(), dp1, -1);
1934             }
1935             for(dp1 = hostp->disks; dp1 != NULL; dp1 = dp1->hostnext) {
1936                 run_server_scripts(EXECUTE_ON_PRE_DLE_AMCHECK,
1937                                    get_config_name(), dp1, -1);
1938             }
1939             start_host(hostp);
1940             hostcount++;
1941             protocol_check();
1942         }
1943     }
1944
1945     protocol_run();
1946
1947     g_fprintf(outf, plural(_("Client check: %d host checked in %s seconds."), 
1948                          _("Client check: %d hosts checked in %s seconds."),
1949                          hostcount),
1950             hostcount, walltime_str(curclock()));
1951     g_fprintf(outf, plural(_("  %d problem found.\n"),
1952                          _("  %d problems found.\n"), remote_errors),
1953             remote_errors);
1954     fflush(outf);
1955
1956     exit(userbad || remote_errors > 0);
1957     /*NOTREACHED*/
1958     return 0;
1959 }
1960
1961 static void
1962 handle_result(
1963     void *              datap,
1964     pkt_t *             pkt,
1965     security_handle_t * sech)
1966 {
1967     am_host_t *hostp;
1968     disk_t *dp;
1969     char *line;
1970     char *s;
1971     char *t;
1972     int ch;
1973     int tch;
1974
1975     hostp = (am_host_t *)datap;
1976     hostp->up = HOST_READY;
1977
1978     if (pkt == NULL) {
1979         g_fprintf(outf,
1980             _("WARNING: %s: selfcheck request failed: %s\n"), hostp->hostname,
1981             security_geterror(sech));
1982         remote_errors++;
1983         hostp->up = HOST_DONE;
1984         return;
1985     }
1986
1987 #if 0
1988     g_fprintf(errf, _("got response from %s:\n----\n%s----\n\n"),
1989             hostp->hostname, pkt->body);
1990 #endif
1991
1992     s = pkt->body;
1993     ch = *s++;
1994     while(ch) {
1995         line = s - 1;
1996         skip_quoted_line(s, ch);
1997         if (s[-2] == '\n') {
1998             s[-2] = '\0';
1999         }
2000
2001         if(strncmp_const(line, "OPTIONS ") == 0) {
2002
2003             t = strstr(line, "features=");
2004             if(t != NULL && (g_ascii_isspace((int)t[-1]) || t[-1] == ';')) {
2005                 char *u = strchr(t, ';');
2006                 if (u)
2007                    *u = '\0';
2008                 t += SIZEOF("features=")-1;
2009                 am_release_feature_set(hostp->features);
2010                 if((hostp->features = am_string_to_feature(t)) == NULL) {
2011                     g_fprintf(outf, _("ERROR: %s: bad features value: '%s'\n"),
2012                             hostp->hostname, t);
2013                     g_fprintf(outf, _("The amfeature in the reply packet is invalid\n"));
2014                     remote_errors++;
2015                     hostp->up = HOST_DONE;
2016                 }
2017                 if (u)
2018                    *u = ';';
2019             }
2020
2021             continue;
2022         }
2023
2024         if(strncmp_const(line, "OK ") == 0) {
2025             continue;
2026         }
2027
2028         t = line;
2029         if(strncmp_const_skip(line, "ERROR ", t, tch) == 0) {
2030             skip_whitespace(t, tch);
2031             /*
2032              * If the "error" is that the "noop" service is unknown, it
2033              * just means the client is "old" (does not support the service).
2034              * We can ignore this.
2035              */
2036             if(!((hostp->features == NULL) && (pkt->type == P_NAK)
2037                && ((strcmp(t - 1, "unknown service: noop") == 0)
2038                    || (strcmp(t - 1, "noop: invalid service") == 0)))) {
2039                 g_fprintf(outf, _("ERROR: %s%s: %s\n"),
2040                         (pkt->type == P_NAK) ? "NAK " : "",
2041                         hostp->hostname,
2042                         t - 1);
2043                 remote_errors++;
2044                 hostp->up = HOST_DONE;
2045             }
2046             continue;
2047         }
2048
2049         g_fprintf(outf, _("ERROR: %s: unknown response: %s\n"),
2050                 hostp->hostname, line);
2051         remote_errors++;
2052         hostp->up = HOST_DONE;
2053     }
2054     if(hostp->up == HOST_READY && hostp->features == NULL) {
2055         /*
2056          * The client does not support the features list, so give it an
2057          * empty one.
2058          */
2059         dbprintf(_("no feature set from host %s\n"), hostp->hostname);
2060         hostp->features = am_set_default_feature_set();
2061     }
2062     for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
2063         if(dp->up == DISK_ACTIVE) {
2064             dp->up = DISK_DONE;
2065         }
2066     }
2067     start_host(hostp);
2068     if(hostp->up == HOST_DONE) {
2069         security_close_connection(sech, hostp->hostname);
2070         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
2071             run_server_scripts(EXECUTE_ON_POST_DLE_AMCHECK,
2072                                get_config_name(), dp, -1);
2073         }
2074         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
2075             run_server_scripts(EXECUTE_ON_POST_HOST_AMCHECK,
2076                                get_config_name(), dp, -1);
2077         }
2078     }
2079     /* try to clean up any defunct processes, since Amanda doesn't wait() for
2080        them explicitly */
2081     while(waitpid(-1, NULL, WNOHANG)> 0);
2082 }