Imported Upstream version 2.5.1
[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 "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_clientchk = 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_time(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 %d instead of %d", 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: tapefile %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: tapefile %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 tape list %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: tape list %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: tape list %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 (strncmp(tapename, "null:", 5) == 0) {
807             fprintf(outf,
808                     "WARNING: tapedev is %s, dumps will be thrown away\n",
809                     tapename);
810             testtape = 0;
811             do_tapechk = 0;
812         }
813     }
814
815     /* check available disk space */
816
817     if(do_localchk) {
818         for(hdp = holdingdisks; hdp != NULL; hdp = hdp->next) {
819             quoted = quote_string(holdingdisk_get_diskdir(hdp));
820             if(get_fs_stats(holdingdisk_get_diskdir(hdp), &fs) == -1) {
821                 fprintf(outf, "ERROR: holding dir %s (%s), "
822                         "you must create a directory.\n",
823                         quoted, strerror(errno));
824                 disklow = 1;
825             }
826             else if(access(holdingdisk_get_diskdir(hdp), W_OK) == -1) {
827                 fprintf(outf, "ERROR: holding disk %s: not writable: %s.\n",
828                         quoted, strerror(errno));
829                 disklow = 1;
830             }
831             else if(access(holdingdisk_get_diskdir(hdp), X_OK) == -1) {
832                 fprintf(outf, "ERROR: holding disk %s: not searcheable: %s.\n",
833                         quoted, strerror(errno));
834                 disklow = 1;
835             }
836             else if(fs.avail == (off_t)-1) {
837                 fprintf(outf,
838                         "WARNING: holding disk %s: "
839                         "available space unknown (" OFF_T_FMT" KB requested)\n",
840                         quoted, (OFF_T_FMT_TYPE)holdingdisk_get_disksize(hdp));
841                 disklow = 1;
842             }
843             else if(holdingdisk_get_disksize(hdp) > (off_t)0) {
844                 if(fs.avail < holdingdisk_get_disksize(hdp)) {
845                     fprintf(outf,
846                             "WARNING: holding disk %s: "
847                             "only " OFF_T_FMT " %sB free ("
848                             OFF_T_FMT " %sB requested)\n", quoted,
849                             (OFF_T_FMT_TYPE)(fs.avail / (off_t)unitdivisor),
850                             displayunit,
851                             (OFF_T_FMT_TYPE)(holdingdisk_get_disksize(hdp)/(off_t)unitdivisor),
852                             displayunit);
853                     disklow = 1;
854                 }
855                 else {
856                     fprintf(outf,
857                             "Holding disk %s: " OFF_T_FMT
858                             " %sB disk space available, that's plenty\n",
859                             quoted, (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
860                             displayunit);
861                 }
862             }
863             else {
864                 assert(holdingdisk_get_disksize(hdp) < (off_t)0);
865                 if((fs.avail + holdingdisk_get_disksize(hdp)) <= (off_t)0) {
866                     fprintf(outf,
867                             "WARNING: holding disk %s: "
868                             "only " OFF_T_FMT " %sB free, using nothing\n",
869                             quoted, (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
870                             displayunit);
871                     disklow = 1;
872                 }
873                 else {
874                     fprintf(outf,
875                             "Holding disk %s: "
876                             OFF_T_FMT " %sB disk space available, using "
877                             OFF_T_FMT " %sB\n",
878                             quoted,
879                             (OFF_T_FMT_TYPE)(fs.avail/(off_t)unitdivisor),
880                             displayunit,
881                             (OFF_T_FMT_TYPE)(fs.avail + holdingdisk_get_disksize(hdp) / (off_t)unitdivisor),
882                             displayunit);
883                 }
884             }
885             amfree(quoted);
886         }
887     }
888
889     /* check that the log file is writable if it already exists */
890
891     if(do_localchk) {
892         char *conf_logdir;
893         char *logfile;
894         char *olddir;
895         struct stat stat_old;
896         struct stat statbuf;
897
898         conf_logdir = getconf_str(CNF_LOGDIR);
899         if (*conf_logdir == '/') {
900             conf_logdir = stralloc(conf_logdir);
901         } else {
902             conf_logdir = stralloc2(config_dir, conf_logdir);
903         }
904         logfile = vstralloc(conf_logdir, "/log", NULL);
905
906         quoted = quote_string(conf_logdir);
907         if(stat(conf_logdir, &statbuf) == -1) {
908             fprintf(outf, "ERROR: logdir %s (%s), you must create directory.\n",
909                     quoted, strerror(errno));
910             disklow = 1;
911         }
912         else if(access(conf_logdir, W_OK) == -1) {
913             fprintf(outf, "ERROR: log dir %s: not writable\n", quoted);
914             logbad = 1;
915         }
916         amfree(quoted);
917
918         if(access(logfile, F_OK) == 0) {
919             testtape = 0;
920             logbad = 1;
921             if(access(logfile, W_OK) != 0) {
922                 quoted = quote_string(logfile);
923                 fprintf(outf, "ERROR: log file %s: not writable\n", quoted);
924                 amfree(quoted);
925             }
926         }
927
928         olddir = vstralloc(conf_logdir, "/oldlog", NULL);
929         quoted = quote_string(olddir);
930         if (stat(olddir,&stat_old) == 0) { /* oldlog exist */
931             if(!(S_ISDIR(stat_old.st_mode))) {
932                 fprintf(outf, "ERROR: oldlog directory %s is not a directory\n",
933                         quoted);
934             }
935             if(access(olddir, W_OK) == -1) {
936                 fprintf(outf, "ERROR: oldlog dir %s: not writable\n", quoted);
937             }
938         }
939         else if(lstat(olddir,&stat_old) == 0) {
940             fprintf(outf, "ERROR: oldlog directory %s is not a directory\n",
941                     quoted);
942         }
943         amfree(quoted);
944
945         if (testtape) {
946             logfile = newvstralloc(logfile, conf_logdir, "/amdump", NULL);
947             if (access(logfile, F_OK) == 0) {
948                 testtape = 0;
949                 logbad = 1;
950             }
951         }
952
953         amfree(olddir);
954         amfree(logfile);
955         amfree(conf_logdir);
956     }
957
958     if (testtape) {
959         /* check that the tape is a valid amanda tape */
960         int tape_status;
961
962         tapedays = getconf_int(CNF_TAPECYCLE);
963         labelstr = getconf_str(CNF_LABELSTR);
964         tapename = getconf_str(CNF_TAPEDEV);
965
966         if (!getconf_seen(CNF_TPCHANGER) && getconf_int(CNF_RUNTAPES) != 1) {
967             fprintf(outf,
968                     "WARNING: if a tape changer is not available, runtapes must be set to 1\n");
969         }
970
971         tape_status = taper_scan(NULL, &label, &datestamp, &tapename,
972                                  FILE_taperscan_output_callback, outf);
973         if (tape_status < 0) {
974             tape_t *exptape = lookup_last_reusable_tape(0);
975             fprintf(outf, "       (expecting ");
976             if(exptape != NULL) fprintf(outf, "tape %s or ", exptape->label);
977             fprintf(outf, "a new tape)\n");
978             tapebad = 1;
979         } else {
980             if (overwrite) {
981                 char *wrlabel_status;
982                 wrlabel_status = tape_wrlabel(tapename, "X", label,
983                                 (unsigned)(tapetype_get_blocksize(tp) * 1024));
984                 if (wrlabel_status != NULL) {
985                     if (tape_status == 3) {
986                         fprintf(outf,
987                                 "ERROR: Could not label brand new tape: %s\n",
988                                 wrlabel_status);
989                     } else {
990                         fprintf(outf,
991                                 "ERROR: tape %s label ok, but is not writable (%s)\n",
992                                 label, wrlabel_status);
993                     }
994                     tapebad = 1;
995                 } else {
996                     if (tape_status != 3) {
997                         fprintf(outf, "Tape %s is writable; rewrote label.\n", label);
998                     } else {
999                         fprintf(outf, "Wrote label %s to brand new tape.\n", label);
1000                     }
1001                 }
1002             } else {
1003                 fprintf(outf, "NOTE: skipping tape-writable test\n");
1004                 if (tape_status == 3) {
1005                     fprintf(outf,
1006                             "Found a brand new tape, will label it %s.\n", 
1007                             label);
1008                 } else {
1009                     fprintf(outf, "Tape %s label ok\n", label);
1010                 }                    
1011             }
1012         }
1013         amfree(tapename);
1014     } else if (do_tapechk) {
1015         fprintf(outf, "WARNING: skipping tape test because amdump or amflush seem to be running\n");
1016         fprintf(outf, "WARNING: if they are not, you must run amcleanup\n");
1017     } else {
1018         fprintf(outf, "NOTE: skipping tape checks\n");
1019     }
1020
1021     /*
1022      * See if the information file and index directory for each client
1023      * and disk is OK.  Since we may be seeing clients and/or disks for
1024      * the first time, these are just warnings, not errors.
1025      */
1026     if(do_localchk) {
1027         char *conf_infofile;
1028         char *conf_indexdir;
1029         char *hostinfodir = NULL;
1030         char *hostindexdir = NULL;
1031         char *diskdir = NULL;
1032         char *infofile = NULL;
1033         struct stat statbuf;
1034         disk_t *dp;
1035         am_host_t *hostp;
1036         int indexdir_checked = 0;
1037         int hostindexdir_checked = 0;
1038         char *host;
1039         char *disk;
1040         int conf_tapecycle, conf_runspercycle;
1041
1042         conf_tapecycle = getconf_int(CNF_TAPECYCLE);
1043         conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
1044
1045         if(conf_tapecycle <= conf_runspercycle) {
1046                 fprintf(outf, "WARNING: tapecycle (%d) <= runspercycle (%d).\n",
1047                         conf_tapecycle, conf_runspercycle);
1048         }
1049
1050         conf_infofile = getconf_str(CNF_INFOFILE);
1051         if (*conf_infofile == '/') {
1052             conf_infofile = stralloc(conf_infofile);
1053         } else {
1054             conf_infofile = stralloc2(config_dir, conf_infofile);
1055         }
1056
1057         conf_indexdir = getconf_str(CNF_INDEXDIR);
1058         if (*conf_indexdir == '/') {
1059             conf_indexdir = stralloc(conf_indexdir);
1060         } else {
1061             conf_indexdir = stralloc2(config_dir, conf_indexdir);
1062         }
1063
1064 #if TEXTDB
1065         quoted = quote_string(conf_infofile);
1066         if(stat(conf_infofile, &statbuf) == -1) {
1067             if (errno == ENOENT) {
1068                 fprintf(outf, "NOTE: conf info dir %s does not exist\n",
1069                         quoted);
1070                 fprintf(outf, "NOTE: it will be created on the next run.\n");
1071             } else {
1072                 fprintf(outf, "ERROR: conf info dir %s (%s)\n",
1073                         quoted, strerror(errno));
1074             }   
1075             amfree(conf_infofile);
1076         } else if (!S_ISDIR(statbuf.st_mode)) {
1077             fprintf(outf, "ERROR: info dir %s: not a directory\n", quoted);
1078             amfree(conf_infofile);
1079             infobad = 1;
1080         } else if (access(conf_infofile, W_OK) == -1) {
1081             fprintf(outf, "ERROR: info dir %s: not writable\n", quoted);
1082             amfree(conf_infofile);
1083             infobad = 1;
1084         } else {
1085             strappend(conf_infofile, "/");
1086         }
1087         amfree(quoted);
1088 #endif
1089
1090         while(!empty(origq)) {
1091             hostp = origq.head->host;
1092             host = sanitise_filename(hostp->hostname);
1093 #if TEXTDB
1094             if(conf_infofile) {
1095                 hostinfodir = newstralloc2(hostinfodir, conf_infofile, host);
1096                 quoted = quote_string(hostinfodir);
1097                 if(stat(hostinfodir, &statbuf) == -1) {
1098                     if (errno == ENOENT) {
1099                         fprintf(outf, "NOTE: host info dir %s does not exist\n",
1100                                 quoted);
1101                         fprintf(outf,
1102                                 "NOTE: it will be created on the next run.\n");
1103                     } else {
1104                         fprintf(outf, "ERROR: host info dir %s (%s)\n",
1105                                 quoted, strerror(errno));
1106                     }   
1107                     amfree(hostinfodir);
1108                 } else if (!S_ISDIR(statbuf.st_mode)) {
1109                     fprintf(outf, "ERROR: info dir %s: not a directory\n",
1110                             quoted);
1111                     amfree(hostinfodir);
1112                     infobad = 1;
1113                 } else if (access(hostinfodir, W_OK) == -1) {
1114                     fprintf(outf, "ERROR: info dir %s: not writable\n", quoted);
1115                     amfree(hostinfodir);
1116                     infobad = 1;
1117                 } else {
1118                     strappend(hostinfodir, "/");
1119                 }
1120                 amfree(quoted);
1121             }
1122 #endif
1123             for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1124                 disk = sanitise_filename(dp->name);
1125 #if TEXTDB
1126                 if(hostinfodir) {
1127                     char *quotedif;
1128
1129                     diskdir = newstralloc2(diskdir, hostinfodir, disk);
1130                     infofile = vstralloc(diskdir, "/", "info", NULL);
1131                     quoted = quote_string(diskdir);
1132                     quotedif = quote_string(infofile);
1133                     if(stat(diskdir, &statbuf) == -1) {
1134                         if (errno == ENOENT) {
1135                             fprintf(outf, "NOTE: info dir %s does not exist\n",
1136                                 quoted);
1137                             fprintf(outf,
1138                                 "NOTE: it will be created on the next run.\n");
1139                         } else {
1140                             fprintf(outf, "ERROR: info dir %s (%s)\n",
1141                                     quoted, strerror(errno));
1142                         }       
1143                     } else if (!S_ISDIR(statbuf.st_mode)) {
1144                         fprintf(outf, "ERROR: info dir %s: not a directory\n",
1145                                 quoted);
1146                         infobad = 1;
1147                     } else if (access(diskdir, W_OK) == -1) {
1148                         fprintf(outf, "ERROR: info dir %s: not writable\n",
1149                                 quoted);
1150                         infobad = 1;
1151                     } else if(stat(infofile, &statbuf) == -1) {
1152                         if (errno == ENOENT) {
1153                             fprintf(outf, "NOTE: info file %s does not exist\n",
1154                                     quotedif);
1155                             fprintf(outf, "NOTE: it will be created on the next run.\n");
1156                         } else {
1157                             fprintf(outf, "ERROR: info dir %s (%s)\n",
1158                                 quoted, strerror(errno));
1159                         }       
1160                     } else if (!S_ISREG(statbuf.st_mode)) {
1161                         fprintf(outf, "ERROR: info file %s: not a file\n",
1162                                 quotedif);
1163                         infobad = 1;
1164                     } else if (access(infofile, R_OK) == -1) {
1165                         fprintf(outf, "ERROR: info file %s: not readable\n",
1166                                 quotedif);
1167                         infobad = 1;
1168                     }
1169                     amfree(quotedif);
1170                     amfree(quoted);
1171                     amfree(infofile);
1172                 }
1173 #endif
1174                 if(dp->index) {
1175                     if(! indexdir_checked) {
1176                         quoted = quote_string(conf_indexdir);
1177                         if(stat(conf_indexdir, &statbuf) == -1) {
1178                             if (errno == ENOENT) {
1179                                 fprintf(outf, "NOTE: index dir %s does not exist\n",
1180                                     quoted);
1181                                 fprintf(outf, "NOTE: it will be created on the next run.\n");
1182                             } else {
1183                                 fprintf(outf, "ERROR: index dir %s (%s)\n",
1184                                         quoted, strerror(errno));
1185                             }   
1186                             amfree(conf_indexdir);
1187                         } else if (!S_ISDIR(statbuf.st_mode)) {
1188                             fprintf(outf, "ERROR: index dir %s: not a directory\n",
1189                                     quoted);
1190                             amfree(conf_indexdir);
1191                             indexbad = 1;
1192                         } else if (access(conf_indexdir, W_OK) == -1) {
1193                             fprintf(outf, "ERROR: index dir %s: not writable\n",
1194                                     quoted);
1195                             amfree(conf_indexdir);
1196                             indexbad = 1;
1197                         } else {
1198                             strappend(conf_indexdir, "/");
1199                         }
1200                         indexdir_checked = 1;
1201                         amfree(quoted);
1202                     }
1203                     if(conf_indexdir) {
1204                         if(! hostindexdir_checked) {
1205                             hostindexdir = stralloc2(conf_indexdir, host);
1206                             quoted = quote_string(hostindexdir);
1207                             if(stat(hostindexdir, &statbuf) == -1) {
1208                                 if (errno == ENOENT) {
1209                                     fprintf(outf, "NOTE: index dir %s does not exist\n",
1210                                         quoted);
1211                                     fprintf(outf, "NOTE: it will be created on the next run.\n");
1212                                 } else {
1213                                     fprintf(outf, "ERROR: index dir %s (%s)\n",
1214                                             quoted, strerror(errno));
1215                                 }
1216                                 amfree(hostindexdir);
1217                             } else if (!S_ISDIR(statbuf.st_mode)) {
1218                                 fprintf(outf, "ERROR: index dir %s: not a directory\n",
1219                                         quoted);
1220                                 amfree(hostindexdir);
1221                                 indexbad = 1;
1222                             } else if (access(hostindexdir, W_OK) == -1) {
1223                                 fprintf(outf, "ERROR: index dir %s: not writable\n",
1224                                         quoted);
1225                                 amfree(hostindexdir);
1226                                 indexbad = 1;
1227                             } else {
1228                                 strappend(hostindexdir, "/");
1229                             }
1230                             hostindexdir_checked = 1;
1231                             amfree(quoted);
1232                         }
1233                         if(hostindexdir) {
1234                             diskdir = newstralloc2(diskdir, hostindexdir, disk);
1235                             quoted = quote_string(diskdir);
1236                             if(stat(diskdir, &statbuf) == -1) {
1237                                 if (errno == ENOENT) {
1238                                     fprintf(outf, "NOTE: index dir %s does not exist\n",
1239                                         quoted);
1240                                     fprintf(outf, "NOTE: it will be created on the next run.\n");
1241                                 } else {
1242                                     fprintf(outf, "ERROR: index dir %s (%s)\n",
1243                                         quoted, strerror(errno));
1244                                 }       
1245                             } else if (!S_ISDIR(statbuf.st_mode)) {
1246                                 fprintf(outf, "ERROR: index dir %s: not a directory\n",
1247                                         quoted);
1248                                 indexbad = 1;
1249                             } else if (access(diskdir, W_OK) == -1) {
1250                                 fprintf(outf, "ERROR: index dir %s: is not writable\n",
1251                                         quoted);
1252                                 indexbad = 1;
1253                             }
1254                             amfree(quoted);
1255                         }
1256                     }
1257                 }
1258
1259                 if ( dp->encrypt == ENCRYPT_SERV_CUST ) {
1260                   if ( dp->srv_encrypt[0] == '\0' ) {
1261                     fprintf(outf, "ERROR: server encryption program not specified\n");
1262                     pgmbad = 1;
1263                   }
1264                   else if(access(dp->srv_encrypt, X_OK) == -1) {
1265                     fprintf(outf, "ERROR: %s is not executable, server encryption will not work\n",
1266                             dp->srv_encrypt );
1267                     pgmbad = 1;
1268                   }
1269                 }
1270                 if ( dp->compress == COMP_SERV_CUST ) {
1271                   if ( dp->srvcompprog[0] == '\0' ) {
1272                     fprintf(outf, "ERROR: server custom compression program not specified\n");
1273                     pgmbad = 1;
1274                   }
1275                   else if(access(dp->srvcompprog, X_OK) == -1) {
1276                     quoted = quote_string(dp->srvcompprog);
1277
1278                     fprintf(outf, "ERROR: %s is not executable, server custom compression will not work\n",
1279                             quoted);
1280                     amfree(quoted);
1281                     pgmbad = 1;
1282                   }
1283                 }
1284
1285                 amfree(disk);
1286                 remove_disk(&origq, dp);
1287             }
1288             amfree(host);
1289             amfree(hostindexdir);
1290             hostindexdir_checked = 0;
1291         }
1292         amfree(diskdir);
1293         amfree(hostinfodir);
1294         amfree(conf_infofile);
1295         amfree(conf_indexdir);
1296     }
1297
1298     amfree(datestamp);
1299     amfree(label);
1300     amfree(config_dir);
1301     amfree(config_name);
1302
1303     fprintf(outf, "Server check took %s seconds\n", walltime_str(curclock()));
1304
1305     fflush(outf);
1306
1307     malloc_size_2 = malloc_inuse(&malloc_hist_2);
1308
1309     if(malloc_size_1 != malloc_size_2) {
1310         malloc_list(fd, malloc_hist_1, malloc_hist_2);
1311     }
1312
1313     exit(userbad \
1314          || confbad \
1315          || tapebad \
1316          || disklow \
1317          || logbad \
1318          || infobad \
1319          || indexbad \
1320          || pgmbad);
1321     /*NOTREACHED*/
1322     return 0;
1323 }
1324
1325 /* --------------------------------------------------- */
1326
1327 int remote_errors;
1328 FILE *outf;
1329
1330 static void handle_result(void *, pkt_t *, security_handle_t *);
1331 void start_host(am_host_t *hostp);
1332
1333 #define HOST_READY                              ((void *)0)     /* must be 0 */
1334 #define HOST_ACTIVE                             ((void *)1)
1335 #define HOST_DONE                               ((void *)2)
1336
1337 #define DISK_READY                              ((void *)0)     /* must be 0 */
1338 #define DISK_ACTIVE                             ((void *)1)
1339 #define DISK_DONE                               ((void *)2)
1340
1341 void
1342 start_host(
1343     am_host_t *hostp)
1344 {
1345     disk_t *dp;
1346     char *req = NULL;
1347     size_t req_len = 0;
1348     int disk_count;
1349     const security_driver_t *secdrv;
1350     char number[NUM_STR_SIZE];
1351
1352     if(hostp->up != HOST_READY) {
1353         return;
1354     }
1355
1356     if (strncmp (hostp->hostname,"localhost",9) == 0) {
1357         fprintf(outf,
1358                     "WARNING: Usage of fully qualified hostname recommended for Client %s.\n",
1359                     hostp->hostname);
1360     }
1361
1362     /*
1363      * The first time through here we send a "noop" request.  This will
1364      * return the feature list from the client if it supports that.
1365      * If it does not, handle_result() will set the feature list to an
1366      * empty structure.  In either case, we do the disks on the second
1367      * (and subsequent) pass(es).
1368      */
1369     disk_count = 0;
1370     if(hostp->features != NULL) { /* selfcheck service */
1371         int has_features = am_has_feature(hostp->features,
1372                                           fe_req_options_features);
1373         int has_hostname = am_has_feature(hostp->features,
1374                                           fe_req_options_hostname);
1375         int has_maxdumps = am_has_feature(hostp->features,
1376                                           fe_req_options_maxdumps);
1377         int has_config   = am_has_feature(hostp->features,
1378                                           fe_req_options_config);
1379
1380         if(!am_has_feature(hostp->features, fe_selfcheck_req) &&
1381            !am_has_feature(hostp->features, fe_selfcheck_req_device)) {
1382             fprintf(outf,
1383                     "ERROR: Client %s does not support selfcheck REQ packet.\n",
1384                     hostp->hostname);
1385         }
1386         if(!am_has_feature(hostp->features, fe_selfcheck_rep)) {
1387             fprintf(outf,
1388                     "ERROR: Client %s does not support selfcheck REP packet.\n",
1389                     hostp->hostname);
1390         }
1391         if(!am_has_feature(hostp->features, fe_sendsize_req_options) &&
1392            !am_has_feature(hostp->features, fe_sendsize_req_no_options) &&
1393            !am_has_feature(hostp->features, fe_sendsize_req_device)) {
1394             fprintf(outf,
1395                     "ERROR: Client %s does not support sendsize REQ packet.\n",
1396                     hostp->hostname);
1397         }
1398         if(!am_has_feature(hostp->features, fe_sendsize_rep)) {
1399             fprintf(outf,
1400                     "ERROR: Client %s does not support sendsize REP packet.\n",
1401                     hostp->hostname);
1402         }
1403         if(!am_has_feature(hostp->features, fe_sendbackup_req) &&
1404            !am_has_feature(hostp->features, fe_sendbackup_req_device)) {
1405             fprintf(outf,
1406                    "ERROR: Client %s does not support sendbackup REQ packet.\n",
1407                    hostp->hostname);
1408         }
1409         if(!am_has_feature(hostp->features, fe_sendbackup_rep)) {
1410             fprintf(outf,
1411                    "ERROR: Client %s does not support sendbackup REP packet.\n",
1412                    hostp->hostname);
1413         }
1414
1415         snprintf(number, SIZEOF(number), "%d", hostp->maxdumps);
1416         req = vstralloc("SERVICE ", "selfcheck", "\n",
1417                         "OPTIONS ",
1418                         has_features ? "features=" : "",
1419                         has_features ? our_feature_string : "",
1420                         has_features ? ";" : "",
1421                         has_maxdumps ? "maxdumps=" : "",
1422                         has_maxdumps ? number : "",
1423                         has_maxdumps ? ";" : "",
1424                         has_hostname ? "hostname=" : "",
1425                         has_hostname ? hostp->hostname : "",
1426                         has_hostname ? ";" : "",
1427                         has_config   ? "config=" : "",
1428                         has_config   ? config_name : "",
1429                         has_config   ? ";" : "",
1430                         "\n",
1431                         NULL);
1432
1433         req_len = strlen(req);
1434         req_len += 128;                         /* room for SECURITY ... */
1435         req_len += 256;                         /* room for non-disk answers */
1436         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1437             char *l;
1438             size_t l_len;
1439             char *o;
1440             char *calcsize;
1441             char *qname;
1442             char *qdevice;
1443
1444             if(dp->up != DISK_READY || dp->todo != 1) {
1445                 continue;
1446             }
1447             o = optionstr(dp, hostp->features, outf);
1448             if (o == NULL) {
1449                 remote_errors++;
1450                 continue;
1451             }
1452             qname = quote_string(dp->name); 
1453             qdevice = quote_string(dp->device); 
1454             if ((dp->name && qname[0] == '"') || 
1455                 (dp->device && qdevice[0] == '"')) {
1456                 if(!am_has_feature(hostp->features, fe_interface_quoted_text)) {
1457                     fprintf(outf,
1458                             "WARNING: %s:%s:%s host does not support quoted text\n",
1459                             hostp->hostname, qname, qdevice);
1460                 }
1461             }
1462
1463             if(dp->device) {
1464                 if(!am_has_feature(hostp->features, fe_selfcheck_req_device)) {
1465                     fprintf(outf,
1466                      "ERROR: %s:%s (%s): selfcheck does not support device.\n",
1467                      hostp->hostname, qname, dp->device);
1468                 }
1469                 if(!am_has_feature(hostp->features, fe_sendsize_req_device)) {
1470                     fprintf(outf,
1471                      "ERROR: %s:%s (%s): sendsize does not support device.\n",
1472                      hostp->hostname, qname, dp->device);
1473                 }
1474                 if(!am_has_feature(hostp->features, fe_sendbackup_req_device)) {
1475                     fprintf(outf,
1476                      "ERROR: %s:%s (%s): sendbackup does not support device.\n",
1477                      hostp->hostname, qname, dp->device);
1478                 }
1479             }
1480             if(strncmp(dp->program,"DUMP",4) == 0 || 
1481                strncmp(dp->program,"GNUTAR",6) == 0) {
1482                 if(strcmp(dp->program, "DUMP") == 0 &&
1483                    !am_has_feature(hostp->features, fe_program_dump)) {
1484                     fprintf(outf, "ERROR: %s:%s does not support DUMP.\n",
1485                             hostp->hostname, qname);
1486                 }
1487                 if(strcmp(dp->program, "GNUTAR") == 0 &&
1488                    !am_has_feature(hostp->features, fe_program_gnutar)) {
1489                     fprintf(outf, "ERROR: %s:%s does not support GNUTAR.\n",
1490                             hostp->hostname, qname);
1491                 }
1492                 if(dp->estimate == ES_CALCSIZE &&
1493                    !am_has_feature(hostp->features, fe_calcsize_estimate)) {
1494                     fprintf(outf, "ERROR: %s:%s does not support CALCSIZE for estimate, using CLIENT.\n",
1495                             hostp->hostname, qname);
1496                     dp->estimate = ES_CLIENT;
1497                 }
1498                 if(dp->estimate == ES_CALCSIZE &&
1499                    am_has_feature(hostp->features, fe_selfcheck_calcsize))
1500                     calcsize = "CALCSIZE ";
1501                 else
1502                     calcsize = "";
1503
1504                 if(dp->compress == COMP_CUST &&
1505                    !am_has_feature(hostp->features, fe_options_compress_cust)) {
1506                   fprintf(outf,
1507                           "ERROR: Client %s does not support custom compression.\n",
1508                           hostp->hostname);
1509                 }
1510                 if(dp->encrypt == ENCRYPT_CUST ) {
1511                   if ( !am_has_feature(hostp->features, fe_options_encrypt_cust)) {
1512                     fprintf(outf,
1513                             "ERROR: Client %s does not support data encryption.\n",
1514                             hostp->hostname);
1515                     remote_errors++;
1516                   } else if ( dp->compress == COMP_SERV_FAST || 
1517                               dp->compress == COMP_SERV_BEST ||
1518                               dp->compress == COMP_SERV_CUST ) {
1519                     fprintf(outf,
1520                             "ERROR: %s: Client encryption with server compression is not supported. See amanda.conf(5) for detail.\n", hostp->hostname);
1521                     remote_errors++;
1522                   } 
1523                 }
1524                 if(dp->device) {
1525                     l = vstralloc(calcsize,
1526                                   dp->program, " ",
1527                                   qname, " ",
1528                                   dp->device,
1529                                   " 0 OPTIONS |",
1530                                   o,
1531                                   "\n",
1532                                   NULL);
1533                 }
1534                 else {
1535                     l = vstralloc(calcsize,
1536                                   dp->program, " ",
1537                                   qname,
1538                                   " 0 OPTIONS |",
1539                                   o,
1540                                   "\n",
1541                                   NULL);
1542                 }
1543             } else {
1544                 if(!am_has_feature(hostp->features, fe_program_dumper_api)) {
1545                     fprintf(outf, "ERROR: %s:%s does not support DUMPER-API.\n",
1546                             hostp->hostname, qname);
1547                 }
1548                 l = vstralloc("DUMPER ",
1549                               dp->program, 
1550                               " ",
1551                               qname,
1552                               " ",
1553                               dp->device,
1554                               " 0 OPTIONS |",
1555                               o,
1556                               "\n",
1557                               NULL);
1558             }
1559             amfree(qname);
1560             amfree(qdevice);
1561             l_len = strlen(l);
1562             amfree(o);
1563
1564             strappend(req, l);
1565             req_len += l_len;
1566             amfree(l);
1567             dp->up = DISK_ACTIVE;
1568             disk_count++;
1569         }
1570     }
1571     else { /* noop service */
1572         req = vstralloc("SERVICE ", "noop", "\n",
1573                         "OPTIONS ",
1574                         "features=", our_feature_string, ";",
1575                         "\n",
1576                         NULL);
1577         for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1578             if(dp->up != DISK_READY || dp->todo != 1) {
1579                 continue;
1580             }
1581             disk_count++;
1582         }
1583     }
1584
1585     if(disk_count == 0) {
1586         amfree(req);
1587         hostp->up = HOST_DONE;
1588         return;
1589     }
1590
1591     secdrv = security_getdriver(hostp->disks->security_driver);
1592     if (secdrv == NULL) {
1593         error("could not find security driver '%s' for host '%s'",
1594               hostp->disks->security_driver, hostp->hostname);
1595         /*NOTREACHED*/
1596     }
1597     protocol_sendreq(hostp->hostname, secdrv, amhost_get_security_conf, 
1598                      req, conf_ctimeout, handle_result, hostp);
1599
1600     amfree(req);
1601
1602     hostp->up = HOST_ACTIVE;
1603 }
1604
1605 pid_t
1606 start_client_checks(
1607     int         fd)
1608 {
1609     am_host_t *hostp;
1610     disk_t *dp;
1611     int hostcount;
1612     pid_t pid;
1613     int userbad = 0;
1614
1615     switch(pid = fork()) {
1616     case -1:
1617         error("could not fork client check: %s", strerror(errno));
1618         /*NOTREACHED*/
1619
1620     case 0:
1621         break;
1622
1623     default:
1624         return pid;
1625     }
1626
1627     dup2(fd, 1);
1628     dup2(fd, 2);
1629
1630     set_pname("amcheck-clients");
1631
1632     startclock();
1633
1634     if((outf = fdopen(fd, "w")) == NULL) {
1635         error("fdopen %d: %s", fd, strerror(errno));
1636         /*NOTREACHED*/
1637     }
1638     errf = outf;
1639
1640     fprintf(outf, "\nAmanda Backup Client Hosts Check\n");
1641     fprintf(outf,   "--------------------------------\n");
1642
1643     protocol_init();
1644
1645     hostcount = remote_errors = 0;
1646
1647     for(dp = origq.head; dp != NULL; dp = dp->next) {
1648         hostp = dp->host;
1649         if(hostp->up == HOST_READY && dp->todo == 1) {
1650             start_host(hostp);
1651             hostcount++;
1652             protocol_check();
1653         }
1654     }
1655
1656     protocol_run();
1657
1658     fprintf(outf,
1659      "Client check: %d host%s checked in %s seconds, %d problem%s found\n",
1660             hostcount, (hostcount == 1) ? "" : "s",
1661             walltime_str(curclock()),
1662             remote_errors, (remote_errors == 1) ? "" : "s");
1663     fflush(outf);
1664
1665     amfree(config_dir);
1666     amfree(config_name);
1667
1668     malloc_size_2 = malloc_inuse(&malloc_hist_2);
1669
1670     if(malloc_size_1 != malloc_size_2) {
1671         malloc_list(fd, malloc_hist_1, malloc_hist_2);
1672     }
1673
1674     exit(userbad || remote_errors > 0);
1675     /*NOTREACHED*/
1676     return 0;
1677 }
1678
1679 static void
1680 handle_result(
1681     void *              datap,
1682     pkt_t *             pkt,
1683     security_handle_t * sech)
1684 {
1685     am_host_t *hostp;
1686     disk_t *dp;
1687     char *line;
1688     char *s;
1689     char *t;
1690     int ch;
1691     int tch;
1692
1693     hostp = (am_host_t *)datap;
1694     hostp->up = HOST_READY;
1695
1696     if (pkt == NULL) {
1697         fprintf(outf,
1698             "WARNING: %s: selfcheck request failed: %s\n", hostp->hostname,
1699             security_geterror(sech));
1700         remote_errors++;
1701         hostp->up = HOST_DONE;
1702         return;
1703     }
1704
1705 #if 0
1706     fprintf(errf, "got response from %s:\n----\n%s----\n\n",
1707             hostp->hostname, pkt->body);
1708 #endif
1709
1710     s = pkt->body;
1711     ch = *s++;
1712     while(ch) {
1713         line = s - 1;
1714         skip_quoted_line(s, ch);
1715         if (s[-2] == '\n') {
1716             s[-2] = '\0';
1717         }
1718
1719 #define sc "OPTIONS "
1720         if(strncmp(line, sc, SIZEOF(sc)-1) == 0) {
1721 #undef sc
1722
1723 #define sc "features="
1724             t = strstr(line, sc);
1725             if(t != NULL && (isspace((int)t[-1]) || t[-1] == ';')) {
1726                 t += SIZEOF(sc)-1;
1727 #undef sc
1728                 am_release_feature_set(hostp->features);
1729                 if((hostp->features = am_string_to_feature(t)) == NULL) {
1730                     fprintf(outf, "ERROR: %s: bad features value: %s\n",
1731                             hostp->hostname, line);
1732                 }
1733             }
1734
1735             continue;
1736         }
1737
1738 #define sc "OK "
1739         if(strncmp(line, sc, SIZEOF(sc)-1) == 0) {
1740             continue;
1741 #undef sc
1742         }
1743
1744 #define sc "ERROR "
1745         if(strncmp(line, sc, SIZEOF(sc)-1) == 0) {
1746             t = line + SIZEOF(sc) - 1;
1747             tch = t[-1];
1748 #undef sc
1749
1750             skip_whitespace(t, tch);
1751             /*
1752              * If the "error" is that the "noop" service is unknown, it
1753              * just means the client is "old" (does not support the service).
1754              * We can ignore this.
1755              */
1756             if(!((hostp->features == NULL) && (pkt->type == P_NAK)
1757                && ((strcmp(t - 1, "unknown service: noop") == 0)
1758                    || (strcmp(t - 1, "noop: invalid service") == 0)))) {
1759                 fprintf(outf, "ERROR: %s%s: %s\n",
1760                         (pkt->type == P_NAK) ? "NAK " : "",
1761                         hostp->hostname,
1762                         t - 1);
1763                 remote_errors++;
1764                 hostp->up = HOST_DONE;
1765             }
1766             continue;
1767         }
1768
1769         fprintf(outf, "ERROR: %s: unknown response: %s\n",
1770                 hostp->hostname, line);
1771         remote_errors++;
1772         hostp->up = HOST_DONE;
1773     }
1774     if(hostp->up == HOST_READY && hostp->features == NULL) {
1775         /*
1776          * The client does not support the features list, so give it an
1777          * empty one.
1778          */
1779         dbprintf(("%s: no feature set from host %s\n",
1780                   debug_prefix_time(NULL), hostp->hostname));
1781         hostp->features = am_set_default_feature_set();
1782     }
1783     for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1784         if(dp->up == DISK_ACTIVE) {
1785             dp->up = DISK_DONE;
1786         }
1787     }
1788     start_host(hostp);
1789     if(hostp->up == HOST_DONE)
1790         security_close_connection(sech, hostp->hostname);
1791 }