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