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