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