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