Imported Upstream version 2.5.1p3
[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.2.10 2007/02/05 18:54:13 martinea 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 "statfs.h"
35 #include "diskfile.h"
36 #include "tapefile.h"
37 #include "tapeio.h"
38 #include "changer.h"
39 #include "packet.h"
40 #include "security.h"
41 #include "protocol.h"
42 #include "clock.h"
43 #include "version.h"
44 #include "amindex.h"
45 #include "token.h"
46 #include "taperscan.h"
47 #include "server_util.h"
48 #include "pipespawn.h"
49 #include "amfeatures.h"
50
51 #define BUFFER_SIZE     32768
52
53 static time_t conf_ctimeout;
54 static int overwrite;
55
56 static disklist_t origq;
57
58 static uid_t uid_dumpuser;
59
60 /* local functions */
61
62 void usage(void);
63 pid_t start_client_checks(int fd);
64 pid_t start_server_check(int fd, int do_localchk, int do_tapechk);
65 int main(int argc, char **argv);
66 int test_server_pgm(FILE *outf, char *dir, char *pgm, int suid, uid_t dumpuid);
67
68 void
69 usage(void)
70 {
71     error("Usage: amcheck%s [-am] [-w] [-sclt] [-M <address>] <conf> [host [disk]* ]* [-o configoption]*", versionsuffix());
72     /*NOTREACHED*/
73 }
74
75 static unsigned long malloc_hist_1, malloc_size_1;
76 static unsigned long malloc_hist_2, malloc_size_2;
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     ssize_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 *conffile;
106     char *conf_diskfile;
107     char *dumpuser;
108     struct passwd *pw;
109     uid_t uid_me;
110     int    new_argc,   my_argc;
111     char **new_argv, **my_argv;
112     char *errstr;
113
114     safe_fd(-1, 0);
115     safe_cd();
116
117     set_pname("amcheck");
118
119     /* Don't die when child closes pipe */
120     signal(SIGPIPE, SIG_IGN);
121
122     dbopen(DBG_SUBDIR_SERVER);
123
124     memset(buffer, 0, sizeof(buffer));
125     malloc_size_1 = malloc_inuse(&malloc_hist_1);
126
127     snprintf(pid_str, SIZEOF(pid_str), "%ld", (long)getpid());
128
129     erroutput_type = ERR_INTERACTIVE;
130
131     our_features = am_init_feature_set();
132     our_feature_string = am_feature_to_string(our_features);
133
134     if(geteuid() == 0) {
135         seteuid(getuid());
136     }
137     uid_me = getuid();
138
139     alwaysmail = mailout = overwrite = 0;
140     do_localchk = do_tapechk = do_clientchk = 0;
141     server_probs = client_probs = 0;
142     tempfd = mainfd = -1;
143
144     parse_server_conf(argc, argv, &new_argc, &new_argv);
145     my_argc = new_argc;
146     my_argv = new_argv;
147
148     /* process arguments */
149
150     while((opt = getopt(my_argc, my_argv, "M:mawsclt")) != EOF) {
151         switch(opt) {
152         case 'M':       mailto=stralloc(optarg);
153                         if(!validate_mailto(mailto)){
154                            printf("Invalid characters in mail address\n");
155                            exit(1);
156                         }
157                         /*FALLTHROUGH*/
158         case 'm':       
159 #ifdef MAILER
160                         mailout = 1;
161 #else
162                         printf("You can't use -%c because configure didn't find a mailer.\n",
163                                 opt);
164                         exit(1);
165 #endif
166                         break;
167         case 'a':       
168 #ifdef MAILER
169                         mailout = 1;
170                         alwaysmail = 1;
171 #else
172                         printf("You can't use -%c because configure didn't find a mailer.\n",
173                                 opt);
174                         exit(1);
175 #endif
176                         break;
177         case 's':       do_localchk = do_tapechk = 1;
178                         break;
179         case 'c':       do_clientchk = 1;
180                         break;
181         case 'l':       do_localchk = 1;
182                         break;
183         case 'w':       overwrite = 1;
184                         break;
185         case 't':       do_tapechk = 1;
186                         break;
187         case '?':
188         default:
189             usage();
190         }
191     }
192     my_argc -= optind, my_argv += optind;
193     if(my_argc < 1) usage();
194
195
196     if ((do_localchk | do_clientchk | do_tapechk) == 0) {
197         /* Check everything if individual checks were not asked for */
198         do_localchk = do_clientchk = do_tapechk = 1;
199     }
200
201     if(overwrite)
202         do_tapechk = 1;
203
204     config_name = stralloc(*my_argv);
205
206     config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
207     conffile = stralloc2(config_dir, CONFFILE_NAME);
208     if(read_conffile(conffile)) {
209         error("errors processing config file \"%s\"", conffile);
210         /*NOTREACHED*/
211     }
212
213     dbrename(config_name, DBG_SUBDIR_SERVER);
214
215     report_bad_conf_arg();
216
217     amfree(conffile);
218     if(mailout && !mailto && 
219        (getconf_seen(CNF_MAILTO)==0 || strlen(getconf_str(CNF_MAILTO)) == 0)) {
220         printf("\nNo mail address configured in  amanda.conf\n");
221         if(alwaysmail)        
222                 printf("When using -a option please specify -Maddress also\n\n"); 
223         else
224                 printf("Use -Maddress instead of -m\n\n"); 
225         exit(1);
226     }
227     if(mailout && !mailto)
228     { 
229        if(getconf_seen(CNF_MAILTO) && 
230           strlen(getconf_str(CNF_MAILTO)) > 0) {
231           if(!validate_mailto(getconf_str(CNF_MAILTO))){
232                 printf("\nMail address in amanda.conf has invalid characters"); 
233                 printf("\nNo email will be sent\n"); 
234                 mailout = 0;
235           }
236        }
237        else {
238           printf("\nNo mail address configured in  amanda.conf\n");
239           if(alwaysmail)        
240                 printf("When using -a option please specify -Maddress also\n\n"); 
241           else
242                 printf("Use -Maddress instead of -m\n\n"); 
243           exit(1);
244       }
245     }
246
247     conf_ctimeout = getconf_int(CNF_CTIMEOUT);
248
249     conf_diskfile = getconf_str(CNF_DISKFILE);
250     if (*conf_diskfile == '/') {
251         conf_diskfile = stralloc(conf_diskfile);
252     } else {
253         conf_diskfile = stralloc2(config_dir, conf_diskfile);
254     }
255     if(read_diskfile(conf_diskfile, &origq) < 0) {
256         error("could not load disklist %s", conf_diskfile);
257         /*NOTREACHED*/
258     }
259     errstr = match_disklist(&origq, my_argc-1, my_argv+1);
260     if (errstr) {
261         printf("%s",errstr);
262         amfree(errstr);
263     }
264     amfree(conf_diskfile);
265
266     /*
267      * Make sure we are running as the dump user.
268      */
269     dumpuser = getconf_str(CNF_DUMPUSER);
270     if ((pw = getpwnam(dumpuser)) == NULL) {
271         error("cannot look up dump user \"%s\"", dumpuser);
272         /*NOTREACHED*/
273     }
274     uid_dumpuser = pw->pw_uid;
275     if ((pw = getpwuid(uid_me)) == NULL) {
276         error("cannot look up my own uid (%ld)", (long)uid_me);
277         /*NOTREACHED*/
278     }
279     if (uid_me != uid_dumpuser) {
280         error("running as user \"%s\" instead of \"%s\"",
281               pw->pw_name, dumpuser);
282         /*NOTREACHED*/
283     }
284
285     displayunit = getconf_str(CNF_DISPLAYUNIT);
286     unitdivisor = getconf_unit_divisor();
287
288     /*
289      * If both server and client side checks are being done, the server
290      * check output goes to the main output, while the client check output
291      * goes to a temporary file and is copied to the main output when done.
292      *
293      * If the output is to be mailed, the main output is also a disk file,
294      * otherwise it is stdout.
295      */
296     if(do_clientchk && (do_localchk || do_tapechk)) {
297         /* we need the temp file */
298         tempfname = vstralloc(AMANDA_TMPDIR, "/amcheck.temp.", pid_str, NULL);
299         if((tempfd = open(tempfname, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1) {
300             error("could not open %s: %s", tempfname, strerror(errno));
301             /*NOTREACHED*/
302         }
303         unlink(tempfname);                      /* so it goes away on close */
304         amfree(tempfname);
305     }
306
307     if(mailout) {
308         /* the main fd is a file too */
309         mainfname = vstralloc(AMANDA_TMPDIR, "/amcheck.main.", pid_str, NULL);
310         if((mainfd = open(mainfname, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1) {
311             error("could not open %s: %s", mainfname, strerror(errno));
312             /*NOTREACHED*/
313         }
314         unlink(mainfname);                      /* so it goes away on close */
315         amfree(mainfname);
316     }
317     else
318         /* just use stdout */
319         mainfd = 1;
320
321     /* start server side checks */
322
323     if(do_localchk || do_tapechk)
324         serverchk_pid = start_server_check(mainfd, do_localchk, do_tapechk);
325     else
326         serverchk_pid = 0;
327
328     /* start client side checks */
329
330     if(do_clientchk) {
331         clientchk_pid = start_client_checks((do_localchk || do_tapechk) ? tempfd : mainfd);
332     } else {
333         clientchk_pid = 0;
334     }
335
336     /* wait for child processes and note any problems */
337
338     while(1) {
339         if((pid = wait(&retstat)) == -1) {
340             if(errno == EINTR) continue;
341             else break;
342         } else if(pid == clientchk_pid) {
343             client_probs = WIFSIGNALED(retstat) || WEXITSTATUS(retstat);
344             clientchk_pid = 0;
345         } else if(pid == serverchk_pid) {
346             server_probs = WIFSIGNALED(retstat) || WEXITSTATUS(retstat);
347             serverchk_pid = 0;
348         } else {
349             char number[NUM_STR_SIZE];
350             char *wait_msg = NULL;
351
352             snprintf(number, SIZEOF(number), "%ld", (long)pid);
353             wait_msg = vstralloc("parent: reaped bogus pid ", number, "\n",
354                                  NULL);
355             if (fullwrite(mainfd, wait_msg, strlen(wait_msg)) < 0) {
356                 error("write main file: %s", strerror(errno));
357                 /*NOTREACHED*/
358             }
359             amfree(wait_msg);
360         }
361     }
362
363     /* copy temp output to main output and write tagline */
364
365     if(do_clientchk && (do_localchk || do_tapechk)) {
366         if(lseek(tempfd, (off_t)0, 0) == (off_t)-1) {
367             error("seek temp file: %s", strerror(errno));
368             /*NOTREACHED*/
369         }
370
371         while((size = fullread(tempfd, buffer, SIZEOF(buffer))) > 0) {
372             if (fullwrite(mainfd, buffer, (size_t)size) < 0) {
373                 error("write main file: %s", strerror(errno));
374                 /*NOTREACHED*/
375             }
376         }
377         if(size < 0) {
378             error("read temp file: %s", strerror(errno));
379             /*NOTREACHED*/
380         }
381         aclose(tempfd);
382     }
383
384     version_string = vstralloc("\n",
385                                "(brought to you by Amanda ", version(), ")\n",
386                                NULL);
387     if (fullwrite(mainfd, version_string, strlen(version_string)) < 0) {
388         error("write main file: %s", strerror(errno));
389         /*NOTREACHED*/
390     }
391     amfree(version_string);
392     amfree(config_dir);
393     amfree(config_name);
394     amfree(our_feature_string);
395     am_release_feature_set(our_features);
396     our_features = NULL;
397
398     malloc_size_2 = malloc_inuse(&malloc_hist_2);
399
400     if(malloc_size_1 != malloc_size_2) {
401         malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
402     }
403
404     /* send mail if requested, but only if there were problems */
405 #ifdef MAILER
406
407 #define MAILTO_LIMIT    10
408
409     if((server_probs || client_probs || alwaysmail) && mailout) {
410         int mailfd;
411         int nullfd;
412         int errfd;
413         FILE *ferr;
414         char *subject;
415         char **a;
416         amwait_t retstat;
417         ssize_t r;
418         ssize_t w;
419         char *err = NULL;
420         char *extra_info = NULL;
421         char *line = NULL;
422         int ret;
423         int rc;
424         int sig;
425         char number[NUM_STR_SIZE];
426
427         fflush(stdout);
428         if(lseek(mainfd, (off_t)0, SEEK_SET) == (off_t)-1) {
429             error("lseek main file: %s", strerror(errno));
430             /*NOTREACHED*/
431         }
432         if(alwaysmail && !(server_probs || client_probs)) {
433             subject = stralloc2(getconf_str(CNF_ORG),
434                             " AMCHECK REPORT: NO PROBLEMS FOUND");
435         } else {
436             subject = stralloc2(getconf_str(CNF_ORG),
437                             " AMANDA PROBLEM: FIX BEFORE RUN, IF POSSIBLE");
438         }
439         /*
440          * Variable arg lists are hard to deal with when we do not know
441          * ourself how many args are involved.  Split the address list
442          * and hope there are not more than 9 entries.
443          *
444          * Remember that split() returns the original input string in
445          * argv[0], so we have to skip over that.
446          */
447         a = (char **) alloc((MAILTO_LIMIT + 1) * SIZEOF(char *));
448         memset(a, 0, (MAILTO_LIMIT + 1) * SIZEOF(char *));
449         if(mailto) {
450             a[1] = mailto;
451             a[2] = NULL;
452         } else {
453             r = (ssize_t)split(getconf_str(CNF_MAILTO), a, MAILTO_LIMIT, " ");
454             a[r + 1] = NULL;
455         }
456         if((nullfd = open("/dev/null", O_RDWR)) < 0) {
457             error("nullfd: /dev/null: %s", strerror(errno));
458             /*NOTREACHED*/
459         }
460         pipespawn(MAILER, STDIN_PIPE | STDERR_PIPE,
461                             &mailfd, &nullfd, &errfd,
462                             MAILER,
463                             "-s", subject,
464                                   a[1], a[2], a[3], a[4],
465                             a[5], a[6], a[7], a[8], a[9],
466                             NULL);
467         amfree(subject);
468         /*
469          * There is the potential for a deadlock here since we are writing
470          * to the process and then reading stderr, but in the normal case,
471          * nothing should be coming back to us, and hopefully in error
472          * cases, the pipe will break and we will exit out of the loop.
473          */
474         signal(SIGPIPE, SIG_IGN);
475         while((r = fullread(mainfd, buffer, SIZEOF(buffer))) > 0) {
476             if((w = fullwrite(mailfd, buffer, (size_t)r)) != (ssize_t)r) {
477                 if(w < 0 && errno == EPIPE) {
478                     strappend(extra_info, "EPIPE writing to mail process\n");
479                     break;
480                 } else if(w < 0) {
481                     error("mailfd write: %s", strerror(errno));
482                     /*NOTREACHED*/
483                 } else {
484                     error("mailfd write: wrote %zd instead of %zd", w, r);
485                     /*NOTREACHED*/
486                 }
487             }
488         }
489         aclose(mailfd);
490         ferr = fdopen(errfd, "r");
491         if (!ferr) {
492             error("Can't fdopen: %s", strerror(errno));
493             /*NOTREACHED*/
494         }
495         for(; (line = agets(ferr)) != NULL; free(line)) {
496             if (line[0] == '\0')
497                 continue;
498             strappend(extra_info, line);
499             strappend(extra_info, "\n");
500         }
501         afclose(ferr);
502         errfd = -1;
503         rc = 0;
504         while (wait(&retstat) != -1) {
505             if (WIFSIGNALED(retstat)) {
506                     ret = 0;
507                     rc = sig = WTERMSIG(retstat);
508             } else {
509                     sig = 0;
510                     rc = ret = WEXITSTATUS(retstat);
511             }
512             if (rc != 0) {
513                     if (ret == 0) {
514                         strappend(err, "got signal ");
515                         ret = sig;
516                     } else {
517                         strappend(err, "returned ");
518                     }
519                     snprintf(number, SIZEOF(number), "%d", ret);
520                     strappend(err, number);
521             }
522         }
523         if (rc != 0) {
524             if(extra_info) {
525                 fputs(extra_info, stderr);
526                 amfree(extra_info);
527             }
528             error("error running mailer %s: %s", MAILER, err);
529             /*NOTREACHED*/
530         }
531     }
532 #endif
533     free_new_argv(new_argc, new_argv);
534     free_server_config();
535
536     dbclose();
537     return (server_probs || client_probs);
538 }
539
540 /* --------------------------------------------------- */
541
542 int nslots, backwards, found, got_match, tapedays;
543 char *datestamp;
544 char *first_match_label = NULL, *first_match = NULL, *found_device = NULL;
545 char *label;
546 char *searchlabel, *labelstr;
547 tape_t *tp;
548 FILE *errf = NULL;
549
550 int
551 test_server_pgm(
552     FILE *      outf,
553     char *      dir,
554     char *      pgm,
555     int         suid,
556     uid_t       dumpuid)
557 {
558     struct stat statbuf;
559     int pgmbad = 0;
560     char *quoted;
561
562     pgm = vstralloc(dir, "/", pgm, versionsuffix(), NULL);
563     quoted = quote_string(pgm);
564     if(stat(pgm, &statbuf) == -1) {
565         fprintf(outf, "ERROR: program %s: does not exist\n",
566                 quoted);
567         pgmbad = 1;
568     } else if (!S_ISREG(statbuf.st_mode)) {
569         fprintf(outf, "ERROR: program %s: not a file\n",
570                 quoted);
571         pgmbad = 1;
572     } else if (access(pgm, X_OK) == -1) {
573         fprintf(outf, "ERROR: program %s: not executable\n",
574                 quoted);
575         pgmbad = 1;
576     } else if (suid \
577                && dumpuid != 0
578                && (statbuf.st_uid != 0 || (statbuf.st_mode & 04000) == 0)) {
579         fprintf(outf, "ERROR: program %s: not setuid-root\n",
580                 quoted);
581         pgmbad = 1;
582     }
583     amfree(quoted);
584     amfree(pgm);
585     return pgmbad;
586 }
587
588 pid_t
589 start_server_check(
590     int         fd,
591     int         do_localchk,
592     int         do_tapechk)
593 {
594     char *tapename;
595     generic_fs_stats_t fs;
596     FILE *outf = NULL;
597     holdingdisk_t *hdp;
598     pid_t pid;
599     int confbad = 0, tapebad = 0, disklow = 0, logbad = 0;
600     int userbad = 0, infobad = 0, indexbad = 0, pgmbad = 0;
601     int testtape = do_tapechk;
602     tapetype_t *tp = NULL;
603     char *quoted;
604
605     switch(pid = fork()) {
606     case -1:
607         error("could not fork server check: %s", strerror(errno));
608         /*NOTREACHED*/
609
610     case 0:
611         break;
612
613     default:
614         return pid;
615     }
616
617     dup2(fd, 1);
618     dup2(fd, 2);
619
620     set_pname("amcheck-server");
621
622     startclock();
623
624     if((outf = fdopen(fd, "w")) == NULL) {
625         error("fdopen %d: %s", fd, strerror(errno));
626         /*NOTREACHED*/
627     }
628     errf = outf;
629
630     fprintf(outf, "Amanda Tape Server Host Check\n");
631     fprintf(outf, "-----------------------------\n");
632
633     if (do_localchk || testtape) {
634         tp = lookup_tapetype(getconf_str(CNF_TAPETYPE));
635     }
636
637     /*
638      * Check various server side config file settings.
639      */
640     if(do_localchk) {
641         char *ColumnSpec;
642         char *errstr = NULL;
643         char *lbl_templ;
644
645         ColumnSpec = getconf_str(CNF_COLUMNSPEC);
646         if(SetColumDataFromString(ColumnData, ColumnSpec, &errstr) < 0) {
647             fprintf(outf, "ERROR: %s\n", errstr);
648             amfree(errstr);
649             confbad = 1;
650         }
651         lbl_templ = tapetype_get_lbl_templ(tp);
652         if(strcmp(lbl_templ, "") != 0) {
653             if(strchr(lbl_templ, '/') == NULL) {
654                 lbl_templ = stralloc2(config_dir, lbl_templ);
655             } else {
656                 lbl_templ = stralloc(lbl_templ);
657             }
658             if(access(lbl_templ, R_OK) == -1) {
659                 fprintf(outf,
660                         "ERROR: cannot access lbl_templ file %s: %s\n",
661                         lbl_templ,
662                         strerror(errno));
663                 confbad = 1;
664             }
665 #if !defined(LPRCMD)
666             fprintf(outf, "ERROR: lbl_templ set but no LPRCMD defined, you should reconfigure amanda\n       and make sure it find a lpr or lp command.\n");
667             confbad = 1;
668 #endif
669         }
670
671         /* check that localhost is resolvable */
672         if ((gethostbyname("localhost")) == NULL) {
673             fprintf(outf, "ERROR: Cannot resolve `localhost'.\n");
674         }
675     }
676
677     /*
678      * Look up the programs used on the server side.
679      */
680     if(do_localchk) {
681         /* 
682          * entreprise version will do planner/dumper suid check
683          */
684         if(access(libexecdir, X_OK) == -1) {
685             quoted = quote_string(libexecdir);
686             fprintf(outf, "ERROR: program dir %s: not accessible\n",
687                     quoted);
688             pgmbad = 1;
689             amfree(quoted);
690         } else {
691             if(test_server_pgm(outf, libexecdir, "planner", 1, uid_dumpuser))
692                 pgmbad = 1;
693             if(test_server_pgm(outf, libexecdir, "dumper", 1, uid_dumpuser))
694                 pgmbad = 1;
695             if(test_server_pgm(outf, libexecdir, "driver", 0, uid_dumpuser))
696                 pgmbad = 1;
697             if(test_server_pgm(outf, libexecdir, "taper", 0, uid_dumpuser))
698                 pgmbad = 1;
699             if(test_server_pgm(outf, libexecdir, "amtrmidx", 0, uid_dumpuser))
700                 pgmbad = 1;
701             if(test_server_pgm(outf, libexecdir, "amlogroll", 0, uid_dumpuser))
702                 pgmbad = 1;
703         }
704         if(access(sbindir, X_OK) == -1) {
705             quoted = quote_string(sbindir);
706             fprintf(outf, "ERROR: program dir %s: not accessible\n",
707                     sbindir);
708             pgmbad = 1;
709             amfree(quoted);
710         } else {
711             if(test_server_pgm(outf, sbindir, "amgetconf", 0, uid_dumpuser))
712                 pgmbad = 1;
713             if(test_server_pgm(outf, sbindir, "amcheck", 1, uid_dumpuser))
714                 pgmbad = 1;
715             if(test_server_pgm(outf, sbindir, "amdump", 0, uid_dumpuser))
716                 pgmbad = 1;
717             if(test_server_pgm(outf, sbindir, "amreport", 0, uid_dumpuser))
718                 pgmbad = 1;
719         }
720         if(access(COMPRESS_PATH, X_OK) == -1) {
721             quoted = quote_string(COMPRESS_PATH);
722             fprintf(outf, "WARNING: %s is not executable, server-compression and indexing will not work\n",
723                     quoted);
724             amfree(quoted);
725         }
726     }
727
728     /*
729      * Check that the directory for the tapelist file is writable, as well
730      * as the tapelist file itself (if it already exists).  Also, check for
731      * a "hold" file (just because it is convenient to do it here) and warn
732      * if tapedev is set to the null device.
733      */
734
735     if(do_localchk || do_tapechk) {
736         char *conf_tapelist;
737         char *tapefile;
738         char *tape_dir;
739         char *lastslash;
740         char *holdfile;
741         struct stat statbuf;
742         
743         conf_tapelist=getconf_str(CNF_TAPELIST);
744         if (*conf_tapelist == '/') {
745             tapefile = stralloc(conf_tapelist);
746         } else {
747             tapefile = stralloc2(config_dir, conf_tapelist);
748         }
749         /*
750          * XXX There Really Ought to be some error-checking here... dhw
751          */
752         tape_dir = stralloc(tapefile);
753         if ((lastslash = strrchr((const char *)tape_dir, '/')) != NULL) {
754             *lastslash = '\0';
755         /*
756          * else whine Really Loudly about a path with no slashes??!?
757          */
758         }
759         if(access(tape_dir, W_OK) == -1) {
760             quoted = quote_string(tape_dir);
761             fprintf(outf, "ERROR: tapelist dir %s: not writable.\n", quoted);
762             tapebad = 1;
763             amfree(quoted);
764         }
765         else if(stat(tapefile, &statbuf) == -1) {
766             quoted = quote_string(tape_dir);
767             fprintf(outf, "ERROR: tapelist %s (%s), "
768                     "you must create an empty file.\n",
769                     quoted, strerror(errno));
770             tapebad = 1;
771             amfree(quoted);
772         }
773         else if(!S_ISREG(statbuf.st_mode)) {
774             quoted = quote_string(tapefile);
775             fprintf(outf, "ERROR: tapelist %s: should be a regular file.\n",
776                     quoted);
777             tapebad = 1;
778             amfree(quoted);
779         }
780         else if(access(tapefile, F_OK) != 0) {
781             quoted = quote_string(tapefile);
782             fprintf(outf, "ERROR: can't access tapelist %s\n", quoted);
783             tapebad = 1;
784             amfree(quoted);
785         } else if(access(tapefile, F_OK) == 0 && access(tapefile, W_OK) != 0) {
786             quoted = quote_string(tapefile);
787             fprintf(outf, "ERROR: tapelist %s: not writable\n", quoted);
788             tapebad = 1;
789             amfree(quoted);
790         } else if(read_tapelist(tapefile)) {
791             quoted = quote_string(tapefile);
792             fprintf(outf, "ERROR: tapelist %s: parse error\n", quoted);
793             tapebad = 1;
794             amfree(quoted);
795         }
796         holdfile = vstralloc(config_dir, "/", "hold", NULL);
797         if(access(holdfile, F_OK) != -1) {
798             quoted = quote_string(holdfile);
799             fprintf(outf, "WARNING: hold file %s exists\n", holdfile);
800             amfree(quoted);
801         }
802         amfree(tapefile);
803         amfree(tape_dir);
804         amfree(holdfile);
805         tapename = getconf_str(CNF_TAPEDEV);
806         if (tapename == NULL) {
807             if (getconf_str(CNF_TPCHANGER) == NULL) {
808                 fprintf(outf, "WARNING: No tapedev or tpchanger specified\n");
809                 testtape = 0;
810                 do_tapechk = 0;
811             }
812         } else if (strncmp(tapename, "null:", 5) == 0) {
813             fprintf(outf,
814                     "WARNING: tapedev is %s, dumps will be thrown away\n",
815                     tapename);
816             testtape = 0;
817             do_tapechk = 0;
818         }
819     }
820
821     /* check available disk space */
822
823     if(do_localchk) {
824         for(hdp = holdingdisks; hdp != NULL; hdp = hdp->next) {
825             quoted = quote_string(holdingdisk_get_diskdir(hdp));
826             if(get_fs_stats(holdingdisk_get_diskdir(hdp), &fs) == -1) {
827                 fprintf(outf, "ERROR: holding dir %s (%s), "
828                         "you must create a directory.\n",
829                         quoted, strerror(errno));
830                 disklow = 1;
831             }
832             else if(access(holdingdisk_get_diskdir(hdp), W_OK) == -1) {
833                 fprintf(outf, "ERROR: holding disk %s: not writable: %s.\n",
834                         quoted, strerror(errno));
835                 disklow = 1;
836             }
837             else if(access(holdingdisk_get_diskdir(hdp), X_OK) == -1) {
838                 fprintf(outf, "ERROR: holding disk %s: not searcheable: %s.\n",
839                         quoted, strerror(errno));
840                 disklow = 1;
841             }
842             else if(fs.avail == (off_t)-1) {
843                 fprintf(outf,
844                         "WARNING: holding disk %s: "
845                         "available space unknown (" OFF_T_FMT" KB requested)\n",
846                         quoted, (OFF_T_FMT_TYPE)holdingdisk_get_disksize(hdp));
847                 disklow = 1;
848             }
849             else if(holdingdisk_get_disksize(hdp) > (off_t)0) {
850                 if(fs.avail < holdingdisk_get_disksize(hdp)) {
851                     fprintf(outf,
852                             "WARNING: holding disk %s: "
853                             "only " OFF_T_FMT " %sB free ("
854                             OFF_T_FMT " %sB requested)\n", quoted,
855                             (OFF_T_FMT_TYPE)(fs.avail / (off_t)unitdivisor),
856                             displayunit,
857                             (OFF_T_FMT_TYPE)(holdingdisk_get_disksize(hdp)/(off_t)unitdivisor),
858                             displayunit);
859                     disklow = 1;
860                 }
861                 else {
862                     fprintf(outf,
863                             "Holding disk %s: " OFF_T_FMT
864                             " %sB disk space available,"
865                             " using " OFF_T_FMT " %sB as requested\n",
866                             quoted,
867                             (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
868                             displayunit,
869                             (OFF_T_FMT_TYPE)(holdingdisk_get_disksize(hdp)/(off_t)unitdivisor),
870                             displayunit);
871                 }
872             }
873             else {
874                 if((fs.avail + holdingdisk_get_disksize(hdp)) < (off_t)0) {
875                     fprintf(outf,
876                             "WARNING: holding disk %s: "
877                             "only " OFF_T_FMT " %sB free, using nothing\n",
878                             quoted, (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
879                             displayunit);
880                     disklow = 1;
881                 }
882                 else {
883                     fprintf(outf,
884                             "Holding disk %s: "
885                             OFF_T_FMT " %sB disk space available, using "
886                             OFF_T_FMT " %sB\n",
887                             quoted,
888                             (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
889                             displayunit,
890                             (OFF_T_FMT_TYPE)((fs.avail + holdingdisk_get_disksize(hdp)) / (off_t)unitdivisor),
891                             displayunit);
892                 }
893             }
894             amfree(quoted);
895         }
896     }
897
898     /* check that the log file is writable if it already exists */
899
900     if(do_localchk) {
901         char *conf_logdir;
902         char *logfile;
903         char *olddir;
904         struct stat stat_old;
905         struct stat statbuf;
906
907         conf_logdir = getconf_str(CNF_LOGDIR);
908         if (*conf_logdir == '/') {
909             conf_logdir = stralloc(conf_logdir);
910         } else {
911             conf_logdir = stralloc2(config_dir, conf_logdir);
912         }
913         logfile = vstralloc(conf_logdir, "/log", NULL);
914
915         quoted = quote_string(conf_logdir);
916         if(stat(conf_logdir, &statbuf) == -1) {
917             fprintf(outf, "ERROR: logdir %s (%s), you must create directory.\n",
918                     quoted, strerror(errno));
919             disklow = 1;
920         }
921         else if(access(conf_logdir, W_OK) == -1) {
922             fprintf(outf, "ERROR: log dir %s: not writable\n", quoted);
923             logbad = 1;
924         }
925         amfree(quoted);
926
927         if(access(logfile, F_OK) == 0) {
928             testtape = 0;
929             logbad = 1;
930             if(access(logfile, W_OK) != 0) {
931                 quoted = quote_string(logfile);
932                 fprintf(outf, "ERROR: log file %s: not writable\n", quoted);
933                 amfree(quoted);
934             }
935         }
936
937         olddir = vstralloc(conf_logdir, "/oldlog", NULL);
938         quoted = quote_string(olddir);
939         if (stat(olddir,&stat_old) == 0) { /* oldlog exist */
940             if(!(S_ISDIR(stat_old.st_mode))) {
941                 fprintf(outf, "ERROR: oldlog directory %s is not a directory\n",
942                         quoted);
943             }
944             if(access(olddir, W_OK) == -1) {
945                 fprintf(outf, "ERROR: oldlog dir %s: not writable\n", quoted);
946             }
947         }
948         else if(lstat(olddir,&stat_old) == 0) {
949             fprintf(outf, "ERROR: oldlog directory %s is not a directory\n",
950                     quoted);
951         }
952         amfree(quoted);
953
954         if (testtape) {
955             logfile = newvstralloc(logfile, conf_logdir, "/amdump", NULL);
956             if (access(logfile, F_OK) == 0) {
957                 testtape = 0;
958                 logbad = 1;
959             }
960         }
961
962         amfree(olddir);
963         amfree(logfile);
964         amfree(conf_logdir);
965     }
966
967     if (testtape) {
968         /* check that the tape is a valid amanda tape */
969         int tape_status;
970
971         tapedays = getconf_int(CNF_TAPECYCLE);
972         labelstr = getconf_str(CNF_LABELSTR);
973         tapename = getconf_str(CNF_TAPEDEV);
974
975         if (!getconf_seen(CNF_TPCHANGER) && getconf_int(CNF_RUNTAPES) != 1) {
976             fprintf(outf,
977                     "WARNING: if a tape changer is not available, runtapes must be set to 1\n");
978         }
979
980         tape_status = taper_scan(NULL, &label, &datestamp, &tapename,
981                                  FILE_taperscan_output_callback, outf);
982         if (tapename) {
983             if (tape_access(tapename,F_OK) == -1) {
984                 fprintf(outf, "ERROR: Can't access device %s: %s\n", tapename,
985                         strerror(errno));
986             }
987             if (tape_access(tapename,R_OK) == -1) {
988                 fprintf(outf, "ERROR: Can't read device %s: %s\n", tapename,
989                         strerror(errno));
990             }
991             if (tape_access(tapename,W_OK) == -1) {
992                 fprintf(outf, "ERROR: Can't write to device %s: %s\n", tapename,
993                         strerror(errno));
994             }
995         }
996         if (tape_status < 0) {
997             tape_t *exptape = lookup_last_reusable_tape(0);
998             fprintf(outf, "       (expecting ");
999             if(exptape != NULL) fprintf(outf, "tape %s or ", exptape->label);
1000             fprintf(outf, "a new tape)\n");
1001             tapebad = 1;
1002         } else {
1003             if (overwrite) {
1004                 char *wrlabel_status;
1005                 wrlabel_status = tape_wrlabel(tapename, "X", label,
1006                                 (unsigned)(tapetype_get_blocksize(tp) * 1024));
1007                 if (wrlabel_status != NULL) {
1008                     if (tape_status == 3) {
1009                         fprintf(outf,
1010                                 "ERROR: Could not label brand new tape: %s\n",
1011                                 wrlabel_status);
1012                     } else {
1013                         fprintf(outf,
1014                                 "ERROR: tape %s label ok, but is not writable (%s)\n",
1015                                 label, wrlabel_status);
1016                     }
1017                     tapebad = 1;
1018                 } else {
1019                     if (tape_status != 3) {
1020                         fprintf(outf, "Tape %s is writable; rewrote label.\n", label);
1021                     } else {
1022                         fprintf(outf, "Wrote label %s to brand new tape.\n", label);
1023                     }
1024                 }
1025             } else {
1026                 fprintf(outf, "NOTE: skipping tape-writable test\n");
1027                 if (tape_status == 3) {
1028                     fprintf(outf,
1029                             "Found a brand new tape, will label it %s.\n", 
1030                             label);
1031                 } else {
1032                     fprintf(outf, "Tape %s label ok\n", label);
1033                 }                    
1034             }
1035         }
1036         amfree(tapename);
1037     } else if (do_tapechk) {
1038         fprintf(outf, "WARNING: skipping tape test because amdump or amflush seem to be running\n");
1039         fprintf(outf, "WARNING: if they are not, you must run amcleanup\n");
1040     } else {
1041         fprintf(outf, "NOTE: skipping tape checks\n");
1042     }
1043
1044     /*
1045      * See if the information file and index directory for each client
1046      * and disk is OK.  Since we may be seeing clients and/or disks for
1047      * the first time, these are just warnings, not errors.
1048      */
1049     if(do_localchk) {
1050         char *conf_infofile;
1051         char *conf_indexdir;
1052         char *hostinfodir = NULL;
1053         char *hostindexdir = NULL;
1054         char *diskdir = NULL;
1055         char *infofile = NULL;
1056         struct stat statbuf;
1057         disk_t *dp;
1058         am_host_t *hostp;
1059         int indexdir_checked = 0;
1060         int hostindexdir_checked = 0;
1061         char *host;
1062         char *disk;
1063         int conf_tapecycle, conf_runspercycle;
1064
1065         conf_tapecycle = getconf_int(CNF_TAPECYCLE);
1066         conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
1067
1068         if(conf_tapecycle <= conf_runspercycle) {
1069                 fprintf(outf, "WARNING: tapecycle (%d) <= runspercycle (%d).\n",
1070                         conf_tapecycle, conf_runspercycle);
1071         }
1072
1073         conf_infofile = getconf_str(CNF_INFOFILE);
1074         if (*conf_infofile == '/') {
1075             conf_infofile = stralloc(conf_infofile);
1076         } else {
1077             conf_infofile = stralloc2(config_dir, conf_infofile);
1078         }
1079
1080         conf_indexdir = getconf_str(CNF_INDEXDIR);
1081         if (*conf_indexdir == '/') {
1082             conf_indexdir = stralloc(conf_indexdir);
1083         } else {
1084             conf_indexdir = stralloc2(config_dir, conf_indexdir);
1085         }
1086
1087 #if TEXTDB
1088         quoted = quote_string(conf_infofile);
1089         if(stat(conf_infofile, &statbuf) == -1) {
1090             if (errno == ENOENT) {
1091                 fprintf(outf, "NOTE: conf info dir %s does not exist\n",
1092                         quoted);
1093                 fprintf(outf, "NOTE: it will be created on the next run.\n");
1094             } else {
1095                 fprintf(outf, "ERROR: conf info dir %s (%s)\n",
1096                         quoted, strerror(errno));
1097             }   
1098             amfree(conf_infofile);
1099         } else if (!S_ISDIR(statbuf.st_mode)) {
1100             fprintf(outf, "ERROR: info dir %s: not a directory\n", quoted);
1101             amfree(conf_infofile);
1102             infobad = 1;
1103         } else if (access(conf_infofile, W_OK) == -1) {
1104             fprintf(outf, "ERROR: info dir %s: not writable\n", quoted);
1105             amfree(conf_infofile);
1106             infobad = 1;
1107         } else {
1108             char *errmsg = NULL;
1109             if (check_infofile(conf_infofile, &origq, &errmsg) == -1) {
1110                 fprintf(outf, "ERROR: Can't copy infofile: %s\n", errmsg);
1111                 amfree(errmsg);
1112             }
1113             strappend(conf_infofile, "/");
1114         }
1115         amfree(quoted);
1116 #endif
1117
1118         while(!empty(origq)) {
1119             hostp = origq.head->host;
1120             host = sanitise_filename(hostp->hostname);
1121 #if TEXTDB
1122             if(conf_infofile) {
1123                 hostinfodir = newstralloc2(hostinfodir, conf_infofile, host);
1124                 quoted = quote_string(hostinfodir);
1125                 if(stat(hostinfodir, &statbuf) == -1) {
1126                     if (errno == ENOENT) {
1127                         fprintf(outf, "NOTE: host info dir %s does not exist\n",
1128                                 quoted);
1129                         fprintf(outf,
1130                                 "NOTE: it will be created on the next run.\n");
1131                     } else {
1132                         fprintf(outf, "ERROR: host info dir %s (%s)\n",
1133                                 quoted, strerror(errno));
1134                     }   
1135                     amfree(hostinfodir);
1136                 } else if (!S_ISDIR(statbuf.st_mode)) {
1137                     fprintf(outf, "ERROR: info dir %s: not a directory\n",
1138                             quoted);
1139                     amfree(hostinfodir);
1140                     infobad = 1;
1141                 } else if (access(hostinfodir, W_OK) == -1) {
1142                     fprintf(outf, "ERROR: info dir %s: not writable\n", quoted);
1143                     amfree(hostinfodir);
1144                     infobad = 1;
1145                 } else {
1146                     strappend(hostinfodir, "/");
1147                 }
1148                 amfree(quoted);
1149             }
1150 #endif
1151             for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1152                 disk = sanitise_filename(dp->name);
1153 #if TEXTDB
1154                 if(hostinfodir) {
1155                     char *quotedif;
1156
1157                     diskdir = newstralloc2(diskdir, hostinfodir, disk);
1158                     infofile = vstralloc(diskdir, "/", "info", NULL);
1159                     quoted = quote_string(diskdir);
1160                     quotedif = quote_string(infofile);
1161                     if(stat(diskdir, &statbuf) == -1) {
1162                         if (errno == ENOENT) {
1163                             fprintf(outf, "NOTE: info dir %s does not exist\n",
1164                                 quoted);
1165                             fprintf(outf,
1166                                 "NOTE: it will be created on the next run.\n");
1167                         } else {
1168                             fprintf(outf, "ERROR: info dir %s (%s)\n",
1169                                     quoted, strerror(errno));
1170                         }       
1171                     } else if (!S_ISDIR(statbuf.st_mode)) {
1172                         fprintf(outf, "ERROR: info dir %s: not a directory\n",
1173                                 quoted);
1174                         infobad = 1;
1175                     } else if (access(diskdir, W_OK) == -1) {
1176                         fprintf(outf, "ERROR: info dir %s: not writable\n",
1177                                 quoted);
1178                         infobad = 1;
1179                     } else if(stat(infofile, &statbuf) == -1) {
1180                         if (errno == ENOENT) {
1181                             fprintf(outf, "NOTE: info file %s does not exist\n",
1182                                     quotedif);
1183                             fprintf(outf, "NOTE: it will be created on the next run.\n");
1184                         } else {
1185                             fprintf(outf, "ERROR: info dir %s (%s)\n",
1186                                 quoted, strerror(errno));
1187                         }       
1188                     } else if (!S_ISREG(statbuf.st_mode)) {
1189                         fprintf(outf, "ERROR: info file %s: not a file\n",
1190                                 quotedif);
1191                         infobad = 1;
1192                     } else if (access(infofile, R_OK) == -1) {
1193                         fprintf(outf, "ERROR: info file %s: not readable\n",
1194                                 quotedif);
1195                         infobad = 1;
1196                     }
1197                     amfree(quotedif);
1198                     amfree(quoted);
1199                     amfree(infofile);
1200                 }
1201 #endif
1202                 if(dp->index) {
1203                     if(! indexdir_checked) {
1204                         quoted = quote_string(conf_indexdir);
1205                         if(stat(conf_indexdir, &statbuf) == -1) {
1206                             if (errno == ENOENT) {
1207                                 fprintf(outf, "NOTE: index dir %s does not exist\n",
1208                                     quoted);
1209                                 fprintf(outf, "NOTE: it will be created on the next run.\n");
1210                             } else {
1211                                 fprintf(outf, "ERROR: index dir %s (%s)\n",
1212                                         quoted, strerror(errno));
1213                             }   
1214                             amfree(conf_indexdir);
1215                         } else if (!S_ISDIR(statbuf.st_mode)) {
1216                             fprintf(outf, "ERROR: index dir %s: not a directory\n",
1217                                     quoted);
1218                             amfree(conf_indexdir);
1219                             indexbad = 1;
1220                         } else if (access(conf_indexdir, W_OK) == -1) {
1221                             fprintf(outf, "ERROR: index dir %s: not writable\n",
1222                                     quoted);
1223                             amfree(conf_indexdir);
1224                             indexbad = 1;
1225                         } else {
1226                             strappend(conf_indexdir, "/");
1227                         }
1228                         indexdir_checked = 1;
1229                         amfree(quoted);
1230                     }
1231                     if(conf_indexdir) {
1232                         if(! hostindexdir_checked) {
1233                             hostindexdir = stralloc2(conf_indexdir, host);
1234                             quoted = quote_string(hostindexdir);
1235                             if(stat(hostindexdir, &statbuf) == -1) {
1236                                 if (errno == ENOENT) {
1237                                     fprintf(outf, "NOTE: index dir %s does not exist\n",
1238                                         quoted);
1239                                     fprintf(outf, "NOTE: it will be created on the next run.\n");
1240                                 } else {
1241                                     fprintf(outf, "ERROR: index dir %s (%s)\n",
1242                                             quoted, strerror(errno));
1243                                 }
1244                                 amfree(hostindexdir);
1245                             } else if (!S_ISDIR(statbuf.st_mode)) {
1246                                 fprintf(outf, "ERROR: index dir %s: not a directory\n",
1247                                         quoted);
1248                                 amfree(hostindexdir);
1249                                 indexbad = 1;
1250                             } else if (access(hostindexdir, W_OK) == -1) {
1251                                 fprintf(outf, "ERROR: index dir %s: not writable\n",
1252                                         quoted);
1253                                 amfree(hostindexdir);
1254                                 indexbad = 1;
1255                             } else {
1256                                 strappend(hostindexdir, "/");
1257                             }
1258                             hostindexdir_checked = 1;
1259                             amfree(quoted);
1260                         }
1261                         if(hostindexdir) {
1262                             diskdir = newstralloc2(diskdir, hostindexdir, disk);
1263                             quoted = quote_string(diskdir);
1264                             if(stat(diskdir, &statbuf) == -1) {
1265                                 if (errno == ENOENT) {
1266                                     fprintf(outf, "NOTE: index dir %s does not exist\n",
1267                                         quoted);
1268                                     fprintf(outf, "NOTE: it will be created on the next run.\n");
1269                                 } else {
1270                                     fprintf(outf, "ERROR: index dir %s (%s)\n",
1271                                         quoted, strerror(errno));
1272                                 }       
1273                             } else if (!S_ISDIR(statbuf.st_mode)) {
1274                                 fprintf(outf, "ERROR: index dir %s: not a directory\n",
1275                                         quoted);
1276                                 indexbad = 1;
1277                             } else if (access(diskdir, W_OK) == -1) {
1278                                 fprintf(outf, "ERROR: index dir %s: is not writable\n",
1279                                         quoted);
1280                                 indexbad = 1;
1281                             }
1282                             amfree(quoted);
1283                         }
1284                     }
1285                 }
1286
1287                 if ( dp->encrypt == ENCRYPT_SERV_CUST ) {
1288                   if ( dp->srv_encrypt[0] == '\0' ) {
1289                     fprintf(outf, "ERROR: server encryption program not specified\n");
1290                     pgmbad = 1;
1291                   }
1292                   else if(access(dp->srv_encrypt, X_OK) == -1) {
1293                     fprintf(outf, "ERROR: %s is not executable, server encryption will not work\n",
1294                             dp->srv_encrypt );
1295                     pgmbad = 1;
1296                   }
1297                 }
1298                 if ( dp->compress == COMP_SERV_CUST ) {
1299                   if ( dp->srvcompprog[0] == '\0' ) {
1300                     fprintf(outf, "ERROR: server custom compression program not specified\n");
1301                     pgmbad = 1;
1302                   }
1303                   else if(access(dp->srvcompprog, X_OK) == -1) {
1304                     quoted = quote_string(dp->srvcompprog);
1305
1306                     fprintf(outf, "ERROR: %s is not executable, server custom compression will not work\n",
1307                             quoted);
1308                     amfree(quoted);
1309                     pgmbad = 1;
1310                   }
1311                 }
1312
1313                 amfree(disk);
1314                 remove_disk(&origq, dp);
1315             }
1316             amfree(host);
1317             amfree(hostindexdir);
1318             hostindexdir_checked = 0;
1319         }
1320         amfree(diskdir);
1321         amfree(hostinfodir);
1322         amfree(conf_infofile);
1323         amfree(conf_indexdir);
1324     }
1325
1326     amfree(datestamp);
1327     amfree(label);
1328     amfree(config_dir);
1329     amfree(config_name);
1330
1331     fprintf(outf, "Server check took %s seconds\n", walltime_str(curclock()));
1332
1333     fflush(outf);
1334
1335     malloc_size_2 = malloc_inuse(&malloc_hist_2);
1336
1337     if(malloc_size_1 != malloc_size_2) {
1338         malloc_list(fd, malloc_hist_1, malloc_hist_2);
1339     }
1340
1341     exit(userbad \
1342          || confbad \
1343          || tapebad \
1344          || disklow \
1345          || logbad \
1346          || infobad \
1347          || indexbad \
1348          || pgmbad);
1349     /*NOTREACHED*/
1350     return 0;
1351 }
1352
1353 /* --------------------------------------------------- */
1354
1355 int remote_errors;
1356 FILE *outf;
1357
1358 static void handle_result(void *, pkt_t *, security_handle_t *);
1359 void start_host(am_host_t *hostp);
1360
1361 #define HOST_READY                              ((void *)0)     /* must be 0 */
1362 #define HOST_ACTIVE                             ((void *)1)
1363 #define HOST_DONE                               ((void *)2)
1364
1365 #define DISK_READY                              ((void *)0)     /* must be 0 */
1366 #define DISK_ACTIVE                             ((void *)1)
1367 #define DISK_DONE                               ((void *)2)
1368
1369 void
1370 start_host(
1371     am_host_t *hostp)
1372 {
1373     disk_t *dp;
1374     char *req = NULL;
1375     size_t req_len = 0;
1376     int disk_count;
1377     const security_driver_t *secdrv;
1378     char number[NUM_STR_SIZE];
1379
1380     if(hostp->up != HOST_READY) {
1381         return;
1382     }
1383
1384     if (strncmp (hostp->hostname,"localhost",9) == 0) {
1385         fprintf(outf,
1386                     "WARNING: Usage of fully qualified hostname recommended for Client %s.\n",
1387                     hostp->hostname);
1388     }
1389
1390     /*
1391      * The first time through here we send a "noop" request.  This will
1392      * return the feature list from the client if it supports that.
1393      * If it does not, handle_result() will set the feature list to an
1394      * empty structure.  In either case, we do the disks on the second
1395      * (and subsequent) pass(es).
1396      */
1397     disk_count = 0;
1398     if(hostp->features != NULL) { /* selfcheck service */
1399         int has_features = am_has_feature(hostp->features,
1400                                           fe_req_options_features);
1401         int has_hostname = am_has_feature(hostp->features,
1402                                           fe_req_options_hostname);
1403         int has_maxdumps = am_has_feature(hostp->features,
1404                                           fe_req_options_maxdumps);
1405         int has_config   = am_has_feature(hostp->features,
1406                                           fe_req_options_config);
1407
1408         if(!am_has_feature(hostp->features, fe_selfcheck_req) &&
1409            !am_has_feature(hostp->features, fe_selfcheck_req_device)) {
1410             fprintf(outf,
1411                     "ERROR: Client %s does not support selfcheck REQ packet.\n",
1412                     hostp->hostname);
1413         }
1414         if(!am_has_feature(hostp->features, fe_selfcheck_rep)) {
1415             fprintf(outf,
1416                     "ERROR: Client %s does not support selfcheck REP packet.\n",
1417                     hostp->hostname);
1418         }
1419         if(!am_has_feature(hostp->features, fe_sendsize_req_options) &&
1420            !am_has_feature(hostp->features, fe_sendsize_req_no_options) &&
1421            !am_has_feature(hostp->features, fe_sendsize_req_device)) {
1422             fprintf(outf,
1423                     "ERROR: Client %s does not support sendsize REQ packet.\n",
1424                     hostp->hostname);
1425         }
1426         if(!am_has_feature(hostp->features, fe_sendsize_rep)) {
1427             fprintf(outf,
1428                     "ERROR: Client %s does not support sendsize REP packet.\n",
1429                     hostp->hostname);
1430         }
1431         if(!am_has_feature(hostp->features, fe_sendbackup_req) &&
1432            !am_has_feature(hostp->features, fe_sendbackup_req_device)) {
1433             fprintf(outf,
1434                    "ERROR: Client %s does not support sendbackup REQ packet.\n",
1435                    hostp->hostname);
1436         }
1437         if(!am_has_feature(hostp->features, fe_sendbackup_rep)) {
1438             fprintf(outf,
1439                    "ERROR: Client %s does not support sendbackup REP packet.\n",
1440                    hostp->hostname);
1441         }
1442
1443         snprintf(number, SIZEOF(number), "%d", hostp->maxdumps);
1444         req = vstralloc("SERVICE ", "selfcheck", "\n",
1445                         "OPTIONS ",
1446                         has_features ? "features=" : "",
1447                         has_features ? our_feature_string : "",
1448                         has_features ? ";" : "",
1449                         has_maxdumps ? "maxdumps=" : "",
1450                         has_maxdumps ? number : "",
1451                         has_maxdumps ? ";" : "",
1452                         has_hostname ? "hostname=" : "",
1453                         has_hostname ? hostp->hostname : "",
1454                         has_hostname ? ";" : "",
1455                         has_config   ? "config=" : "",
1456                         has_config   ? config_name : "",
1457                         has_config   ? ";" : "",
1458                         "\n",
1459                         NULL);
1460
1461         req_len = strlen(req);
1462         req_len += 128;                         /* room for SECURITY ... */
1463         req_len += 256;                         /* room for non-disk answers */
1464         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1465             char *l;
1466             size_t l_len;
1467             char *o;
1468             char *calcsize;
1469             char *qname;
1470             char *qdevice;
1471
1472             if(dp->up != DISK_READY || dp->todo != 1) {
1473                 continue;
1474             }
1475             o = optionstr(dp, hostp->features, outf);
1476             if (o == NULL) {
1477                 remote_errors++;
1478                 continue;
1479             }
1480             qname = quote_string(dp->name); 
1481             qdevice = quote_string(dp->device); 
1482             if ((dp->name && qname[0] == '"') || 
1483                 (dp->device && qdevice[0] == '"')) {
1484                 if(!am_has_feature(hostp->features, fe_interface_quoted_text)) {
1485                     fprintf(outf,
1486                             "WARNING: %s:%s:%s host does not support quoted text\n",
1487                             hostp->hostname, qname, qdevice);
1488                 }
1489             }
1490
1491             if(dp->device) {
1492                 if(!am_has_feature(hostp->features, fe_selfcheck_req_device)) {
1493                     fprintf(outf,
1494                      "ERROR: %s:%s (%s): selfcheck does not support device.\n",
1495                      hostp->hostname, qname, dp->device);
1496                 }
1497                 if(!am_has_feature(hostp->features, fe_sendsize_req_device)) {
1498                     fprintf(outf,
1499                      "ERROR: %s:%s (%s): sendsize does not support device.\n",
1500                      hostp->hostname, qname, dp->device);
1501                 }
1502                 if(!am_has_feature(hostp->features, fe_sendbackup_req_device)) {
1503                     fprintf(outf,
1504                      "ERROR: %s:%s (%s): sendbackup does not support device.\n",
1505                      hostp->hostname, qname, dp->device);
1506                 }
1507             }
1508             if(strncmp(dp->program,"DUMP",4) == 0 || 
1509                strncmp(dp->program,"GNUTAR",6) == 0) {
1510                 if(strcmp(dp->program, "DUMP") == 0 &&
1511                    !am_has_feature(hostp->features, fe_program_dump)) {
1512                     fprintf(outf, "ERROR: %s:%s does not support DUMP.\n",
1513                             hostp->hostname, qname);
1514                 }
1515                 if(strcmp(dp->program, "GNUTAR") == 0 &&
1516                    !am_has_feature(hostp->features, fe_program_gnutar)) {
1517                     fprintf(outf, "ERROR: %s:%s does not support GNUTAR.\n",
1518                             hostp->hostname, qname);
1519                 }
1520                 if(dp->estimate == ES_CALCSIZE &&
1521                    !am_has_feature(hostp->features, fe_calcsize_estimate)) {
1522                     fprintf(outf, "ERROR: %s:%s does not support CALCSIZE for estimate, using CLIENT.\n",
1523                             hostp->hostname, qname);
1524                     dp->estimate = ES_CLIENT;
1525                 }
1526                 if(dp->estimate == ES_CALCSIZE &&
1527                    am_has_feature(hostp->features, fe_selfcheck_calcsize))
1528                     calcsize = "CALCSIZE ";
1529                 else
1530                     calcsize = "";
1531
1532                 if(dp->compress == COMP_CUST &&
1533                    !am_has_feature(hostp->features, fe_options_compress_cust)) {
1534                   fprintf(outf,
1535                           "ERROR: Client %s does not support custom compression.\n",
1536                           hostp->hostname);
1537                 }
1538                 if(dp->encrypt == ENCRYPT_CUST ) {
1539                   if ( !am_has_feature(hostp->features, fe_options_encrypt_cust)) {
1540                     fprintf(outf,
1541                             "ERROR: Client %s does not support data encryption.\n",
1542                             hostp->hostname);
1543                     remote_errors++;
1544                   } else if ( dp->compress == COMP_SERV_FAST || 
1545                               dp->compress == COMP_SERV_BEST ||
1546                               dp->compress == COMP_SERV_CUST ) {
1547                     fprintf(outf,
1548                             "ERROR: %s: Client encryption with server compression is not supported. See amanda.conf(5) for detail.\n", hostp->hostname);
1549                     remote_errors++;
1550                   } 
1551                 }
1552                 if(dp->device) {
1553                     l = vstralloc(calcsize,
1554                                   dp->program, " ",
1555                                   qname, " ",
1556                                   dp->device,
1557                                   " 0 OPTIONS |",
1558                                   o,
1559                                   "\n",
1560                                   NULL);
1561                 }
1562                 else {
1563                     l = vstralloc(calcsize,
1564                                   dp->program, " ",
1565                                   qname,
1566                                   " 0 OPTIONS |",
1567                                   o,
1568                                   "\n",
1569                                   NULL);
1570                 }
1571             } else {
1572                 if(!am_has_feature(hostp->features, fe_program_dumper_api)) {
1573                     fprintf(outf, "ERROR: %s:%s does not support DUMPER-API.\n",
1574                             hostp->hostname, qname);
1575                 }
1576                 l = vstralloc("DUMPER ",
1577                               dp->program, 
1578                               " ",
1579                               qname,
1580                               " ",
1581                               dp->device,
1582                               " 0 OPTIONS |",
1583                               o,
1584                               "\n",
1585                               NULL);
1586             }
1587             amfree(qname);
1588             amfree(qdevice);
1589             l_len = strlen(l);
1590             amfree(o);
1591
1592             strappend(req, l);
1593             req_len += l_len;
1594             amfree(l);
1595             dp->up = DISK_ACTIVE;
1596             disk_count++;
1597         }
1598     }
1599     else { /* noop service */
1600         req = vstralloc("SERVICE ", "noop", "\n",
1601                         "OPTIONS ",
1602                         "features=", our_feature_string, ";",
1603                         "\n",
1604                         NULL);
1605         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1606             if(dp->up != DISK_READY || dp->todo != 1) {
1607                 continue;
1608             }
1609             disk_count++;
1610         }
1611     }
1612
1613     if(disk_count == 0) {
1614         amfree(req);
1615         hostp->up = HOST_DONE;
1616         return;
1617     }
1618
1619     secdrv = security_getdriver(hostp->disks->security_driver);
1620     if (secdrv == NULL) {
1621         error("could not find security driver '%s' for host '%s'",
1622               hostp->disks->security_driver, hostp->hostname);
1623         /*NOTREACHED*/
1624     }
1625     protocol_sendreq(hostp->hostname, secdrv, amhost_get_security_conf, 
1626                      req, conf_ctimeout, handle_result, hostp);
1627
1628     amfree(req);
1629
1630     hostp->up = HOST_ACTIVE;
1631 }
1632
1633 pid_t
1634 start_client_checks(
1635     int         fd)
1636 {
1637     am_host_t *hostp;
1638     disk_t *dp;
1639     int hostcount;
1640     pid_t pid;
1641     int userbad = 0;
1642
1643     switch(pid = fork()) {
1644     case -1:
1645         error("could not fork client check: %s", strerror(errno));
1646         /*NOTREACHED*/
1647
1648     case 0:
1649         break;
1650
1651     default:
1652         return pid;
1653     }
1654
1655     dup2(fd, 1);
1656     dup2(fd, 2);
1657
1658     set_pname("amcheck-clients");
1659
1660     startclock();
1661
1662     if((outf = fdopen(fd, "w")) == NULL) {
1663         error("fdopen %d: %s", fd, strerror(errno));
1664         /*NOTREACHED*/
1665     }
1666     errf = outf;
1667
1668     fprintf(outf, "\nAmanda Backup Client Hosts Check\n");
1669     fprintf(outf,   "--------------------------------\n");
1670
1671     protocol_init();
1672
1673     hostcount = remote_errors = 0;
1674
1675     for(dp = origq.head; dp != NULL; dp = dp->next) {
1676         hostp = dp->host;
1677         if(hostp->up == HOST_READY && dp->todo == 1) {
1678             start_host(hostp);
1679             hostcount++;
1680             protocol_check();
1681         }
1682     }
1683
1684     protocol_run();
1685
1686     fprintf(outf,
1687      "Client check: %d host%s checked in %s seconds, %d problem%s found\n",
1688             hostcount, (hostcount == 1) ? "" : "s",
1689             walltime_str(curclock()),
1690             remote_errors, (remote_errors == 1) ? "" : "s");
1691     fflush(outf);
1692
1693     amfree(config_dir);
1694     amfree(config_name);
1695
1696     malloc_size_2 = malloc_inuse(&malloc_hist_2);
1697
1698     if(malloc_size_1 != malloc_size_2) {
1699         malloc_list(fd, malloc_hist_1, malloc_hist_2);
1700     }
1701
1702     exit(userbad || remote_errors > 0);
1703     /*NOTREACHED*/
1704     return 0;
1705 }
1706
1707 static void
1708 handle_result(
1709     void *              datap,
1710     pkt_t *             pkt,
1711     security_handle_t * sech)
1712 {
1713     am_host_t *hostp;
1714     disk_t *dp;
1715     char *line;
1716     char *s;
1717     char *t;
1718     int ch;
1719     int tch;
1720
1721     hostp = (am_host_t *)datap;
1722     hostp->up = HOST_READY;
1723
1724     if (pkt == NULL) {
1725         fprintf(outf,
1726             "WARNING: %s: selfcheck request failed: %s\n", hostp->hostname,
1727             security_geterror(sech));
1728         remote_errors++;
1729         hostp->up = HOST_DONE;
1730         return;
1731     }
1732
1733 #if 0
1734     fprintf(errf, "got response from %s:\n----\n%s----\n\n",
1735             hostp->hostname, pkt->body);
1736 #endif
1737
1738     s = pkt->body;
1739     ch = *s++;
1740     while(ch) {
1741         line = s - 1;
1742         skip_quoted_line(s, ch);
1743         if (s[-2] == '\n') {
1744             s[-2] = '\0';
1745         }
1746
1747 #define sc "OPTIONS "
1748         if(strncmp(line, sc, SIZEOF(sc)-1) == 0) {
1749 #undef sc
1750
1751 #define sc "features="
1752             t = strstr(line, sc);
1753             if(t != NULL && (isspace((int)t[-1]) || t[-1] == ';')) {
1754                 t += SIZEOF(sc)-1;
1755 #undef sc
1756                 am_release_feature_set(hostp->features);
1757                 if((hostp->features = am_string_to_feature(t)) == NULL) {
1758                     fprintf(outf, "ERROR: %s: bad features value: %s\n",
1759                             hostp->hostname, line);
1760                 }
1761             }
1762
1763             continue;
1764         }
1765
1766 #define sc "OK "
1767         if(strncmp(line, sc, SIZEOF(sc)-1) == 0) {
1768             continue;
1769 #undef sc
1770         }
1771
1772 #define sc "ERROR "
1773         if(strncmp(line, sc, SIZEOF(sc)-1) == 0) {
1774             t = line + SIZEOF(sc) - 1;
1775             tch = t[-1];
1776 #undef sc
1777
1778             skip_whitespace(t, tch);
1779             /*
1780              * If the "error" is that the "noop" service is unknown, it
1781              * just means the client is "old" (does not support the service).
1782              * We can ignore this.
1783              */
1784             if(!((hostp->features == NULL) && (pkt->type == P_NAK)
1785                && ((strcmp(t - 1, "unknown service: noop") == 0)
1786                    || (strcmp(t - 1, "noop: invalid service") == 0)))) {
1787                 fprintf(outf, "ERROR: %s%s: %s\n",
1788                         (pkt->type == P_NAK) ? "NAK " : "",
1789                         hostp->hostname,
1790                         t - 1);
1791                 remote_errors++;
1792                 hostp->up = HOST_DONE;
1793             }
1794             continue;
1795         }
1796
1797         fprintf(outf, "ERROR: %s: unknown response: %s\n",
1798                 hostp->hostname, line);
1799         remote_errors++;
1800         hostp->up = HOST_DONE;
1801     }
1802     if(hostp->up == HOST_READY && hostp->features == NULL) {
1803         /*
1804          * The client does not support the features list, so give it an
1805          * empty one.
1806          */
1807         dbprintf(("%s: no feature set from host %s\n",
1808                   debug_prefix_time(NULL), hostp->hostname));
1809         hostp->features = am_set_default_feature_set();
1810     }
1811     for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1812         if(dp->up == DISK_ACTIVE) {
1813             dp->up = DISK_DONE;
1814         }
1815     }
1816     start_host(hostp);
1817     if(hostp->up == HOST_DONE)
1818         security_close_connection(sech, hostp->hostname);
1819 }