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