Imported Upstream version 3.2.0
[debian/amanda] / server-src / amindexd.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 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: amindexd.c,v 1.106 2006/07/25 18:27:57 martinea Exp $
28  *
29  * This is the server daemon part of the index client/server system.
30  * It is assumed that this is launched from inetd instead of being
31  * started as a daemon because it is not often used
32  */
33
34 /*
35 ** Notes:
36 ** - this server will do very little until it knows what Amanda config it
37 **   is to use.  Setting the config has the side effect of changing to the
38 **   index directory.
39 ** - XXX - I'm pretty sure the config directory name should have '/'s stripped
40 **   from it.  It is given to us by an unknown person via the network.
41 */
42
43 #include "amanda.h"
44 #include "conffile.h"
45 #include "diskfile.h"
46 #include "arglist.h"
47 #include "clock.h"
48 #include "match.h"
49 #include "amindex.h"
50 #include "disk_history.h"
51 #include "list_dir.h"
52 #include "logfile.h"
53 #include "find.h"
54 #include "tapefile.h"
55 #include "util.h"
56 #include "amandad.h"
57 #include "pipespawn.h"
58 #include "sockaddr-util.h"
59 #include "amxml.h"
60
61 #include <grp.h>
62
63 #define DBG(i, ...) do {                \
64         if ((i) <= debug_amindexd) {    \
65             g_debug(__VA_ARGS__);       \
66         }                               \
67 } while (0)
68
69 typedef struct REMOVE_ITEM
70 {
71     char *filename;
72     struct REMOVE_ITEM *next;
73 } REMOVE_ITEM;
74
75 /* state */
76 static int from_amandad;
77 static char local_hostname[MAX_HOSTNAME_LENGTH+1];      /* me! */
78 static char *dump_hostname = NULL;              /* machine we are restoring */
79 static char *disk_name;                         /* disk we are restoring */
80 char *qdisk_name = NULL;                        /* disk we are restoring */
81 static char *target_date = NULL;
82 static disklist_t disk_list;                    /* all disks in cur config */
83 static find_result_t *output_find = NULL;
84 static g_option_t *g_options = NULL;
85
86 static int amindexd_debug = 0;
87
88 static REMOVE_ITEM *uncompress_remove = NULL;
89                                         /* uncompressed files to remove */
90
91 static am_feature_t *our_features = NULL;
92 static am_feature_t *their_features = NULL;
93
94 static int get_pid_status(int pid, char *program, GPtrArray **emsg);
95 static REMOVE_ITEM *remove_files(REMOVE_ITEM *);
96 static char *uncompress_file(char *, GPtrArray **);
97 static int process_ls_dump(char *, DUMP_ITEM *, int, GPtrArray **);
98
99 static size_t reply_buffer_size = 1;
100 static char *reply_buffer = NULL;
101 static char *amandad_auth = NULL;
102 static FILE *cmdin;
103 static FILE *cmdout;
104
105 static void reply_ptr_array(int, GPtrArray *);
106 static void reply(int, char *, ...) G_GNUC_PRINTF(2, 3);
107 static void lreply(int, char *, ...) G_GNUC_PRINTF(2, 3);
108 static void fast_lreply(int, char *, ...) G_GNUC_PRINTF(2, 3);
109 static am_host_t *is_dump_host_valid(char *);
110 static int is_disk_valid(char *);
111 static int check_and_load_config(char *);
112 static int build_disk_table(void);
113 static int disk_history_list(void);
114 static int is_dir_valid_opaque(char *);
115 static int opaque_ls(char *, int);
116 static void opaque_ls_one (DIR_ITEM *dir_item, am_feature_e marshall_feature,
117                              int recursive);
118 static int tapedev_is(void);
119 static int are_dumps_compressed(void);
120 static char *amindexd_nicedate (char *datestamp);
121 static int cmp_date (const char *date1, const char *date2);
122 static char *get_index_name(char *dump_hostname, char *hostname,
123                             char *diskname, char *timestamps, int level);
124 static int get_index_dir(char *dump_hostname, char *hostname, char *diskname);
125
126 int main(int, char **);
127
128
129 static int
130 get_pid_status(
131     int         pid,
132     char       *program,
133     GPtrArray **emsg)
134 {
135     int       status;
136     amwait_t  wait_status;
137     char     *msg;
138     int       result = 1;
139
140     status = waitpid(pid, &wait_status, 0);
141     if (status < 0) {
142         msg = vstrallocf(
143                 _("%s (%d) returned negative value: %s"),
144                 program, pid, strerror(errno));
145         dbprintf("%s\n", msg);
146         g_ptr_array_add(*emsg, msg);
147         result = 0;
148     } else {
149         if (!WIFEXITED(wait_status)) {
150             msg = vstrallocf(
151                         _("%s exited with signal %d"),
152                         program, WTERMSIG(wait_status));
153             dbprintf("%s\n", msg);
154             g_ptr_array_add(*emsg, msg);
155             result = 0;
156         } else if (WEXITSTATUS(wait_status) != 0) {
157             msg = vstrallocf(
158                         _("%s exited with status %d"),
159                         program, WEXITSTATUS(wait_status));
160             dbprintf("%s\n", msg);
161             g_ptr_array_add(*emsg, msg);
162             result = 0;
163         }
164     }
165     return result;
166 }
167
168 static REMOVE_ITEM *
169 remove_files(
170     REMOVE_ITEM *remove)
171 {
172     REMOVE_ITEM *prev;
173
174     while(remove) {
175         dbprintf(_("removing index file: %s\n"), remove->filename);
176         unlink(remove->filename);
177         amfree(remove->filename);
178         prev = remove;
179         remove = remove->next;
180         amfree(prev);
181     }
182     return remove;
183 }
184
185 static char *
186 uncompress_file(
187     char       *filename_gz,
188     GPtrArray **emsg)
189 {
190     char *cmd = NULL;
191     char *filename = NULL;
192     struct stat stat_filename;
193     int result;
194     size_t len;
195     int pipe_from_gzip;
196     int pipe_to_sort;
197     int indexfd;
198     int nullfd;
199     int uncompress_errfd;
200     int sort_errfd;
201     char line[STR_SIZE];
202     FILE *pipe_stream;
203     pid_t pid_gzip;
204     pid_t pid_sort;
205     pid_t pid_index;
206     int        status;
207     char      *msg;
208     gpointer  *p;
209     gpointer  *p_last;
210     GPtrArray *uncompress_err;
211     GPtrArray *sort_err;
212     FILE      *uncompress_err_stream;
213     FILE      *sort_err_stream;
214
215     filename = stralloc(filename_gz);
216     len = strlen(filename);
217     if(len > 3 && strcmp(&(filename[len-3]),".gz")==0) {
218         filename[len-3]='\0';
219     } else if(len > 2 && strcmp(&(filename[len-2]),".Z")==0) {
220         filename[len-2]='\0';
221     }
222
223     /* uncompress the file */
224     result=stat(filename,&stat_filename);
225     if(result==-1 && errno==ENOENT) {           /* file does not exist */
226         struct stat statbuf;
227         REMOVE_ITEM *remove_file;
228
229         /*
230          * Check that compressed file exists manually.
231          */
232         if (stat(filename_gz, &statbuf) < 0) {
233             msg = vstrallocf(_("Compressed file '%s' is inaccessable: %s"),
234                              filename_gz, strerror(errno));
235             dbprintf("%s\n", msg);
236             g_ptr_array_add(*emsg, msg);
237             amfree(filename);
238             return NULL;
239         }
240
241 #ifdef UNCOMPRESS_OPT
242 #  define PARAM_UNCOMPRESS_OPT UNCOMPRESS_OPT
243 #else
244 #  define PARAM_UNCOMPRESS_OPT skip_argument
245 #endif
246
247         nullfd = open("/dev/null", O_RDONLY);
248
249         indexfd = open(filename,O_WRONLY|O_CREAT, 0600);
250         if (indexfd == -1) {
251             msg = vstrallocf(_("Can't open '%s' for writting: %s"),
252                               filename, strerror(errno));
253             dbprintf("%s\n", msg);
254             g_ptr_array_add(*emsg, msg);
255             amfree(filename);
256             aclose(nullfd);
257             return NULL;
258         }
259
260         /* just use our stderr directly for the pipe's stderr; in 
261          * main() we send stderr to the debug file, or /dev/null
262          * if debugging is disabled */
263
264         /* start the uncompress process */
265         putenv(stralloc("LC_ALL=C"));
266         pid_gzip = pipespawn(UNCOMPRESS_PATH, STDOUT_PIPE|STDERR_PIPE, 0,
267                              &nullfd, &pipe_from_gzip, &uncompress_errfd,
268                              UNCOMPRESS_PATH, PARAM_UNCOMPRESS_OPT,
269                              filename_gz, NULL);
270         aclose(nullfd);
271
272         pipe_stream = fdopen(pipe_from_gzip,"r");
273         if(pipe_stream == NULL) {
274             msg = vstrallocf(_("Can't fdopen pipe from gzip: %s"),
275                              strerror(errno));
276             dbprintf("%s\n", msg);
277             g_ptr_array_add(*emsg, msg);
278             amfree(filename);
279             aclose(indexfd);
280             return NULL;
281         }
282
283         /* start the sort process */
284         putenv(stralloc("LC_ALL=C"));
285         pid_sort = pipespawn(SORT_PATH, STDIN_PIPE|STDERR_PIPE, 0,
286                              &pipe_to_sort, &indexfd, &sort_errfd,
287                              SORT_PATH, NULL);
288         aclose(indexfd);
289
290         /* start a subprocess */
291         /* send all ouput from uncompress process to sort process */
292         pid_index = fork();
293         switch (pid_index) {
294         case -1:
295             msg = vstrallocf(
296                         _("fork error: %s"),
297                         strerror(errno));
298             dbprintf("%s\n", msg);
299             g_ptr_array_add(*emsg, msg);
300             unlink(filename);
301             amfree(filename);
302         default: break;
303         case 0:
304             while (fgets(line, STR_SIZE, pipe_stream) != NULL) {
305                 if (line[0] != '\0') {
306                     if (strchr(line,'/')) {
307                         full_write(pipe_to_sort,line,strlen(line));
308                     }
309                 }
310             }
311             exit(0);
312         }
313
314         fclose(pipe_stream);
315         aclose(pipe_to_sort);
316
317         uncompress_err_stream = fdopen(uncompress_errfd, "r");
318         uncompress_err = g_ptr_array_new();
319         while (fgets(line, sizeof(line), uncompress_err_stream) != NULL) {
320             if (line[strlen(line)-1] == '\n')
321                 line[strlen(line)-1] = '\0';
322             g_ptr_array_add(uncompress_err, vstrallocf("  %s", line));
323             dbprintf("Uncompress: %s\n", line);
324         }
325         fclose(uncompress_err_stream);
326
327         sort_err_stream = fdopen(sort_errfd, "r");
328         sort_err = g_ptr_array_new();
329         while (fgets(line, sizeof(line), sort_err_stream) != NULL) {
330             if (line[strlen(line)-1] == '\n')
331                 line[strlen(line)-1] = '\0';
332             g_ptr_array_add(sort_err, vstrallocf("  %s", line));
333             dbprintf("Sort: %s\n", line);
334         }
335         fclose(sort_err_stream);
336
337         status = get_pid_status(pid_gzip, UNCOMPRESS_PATH, emsg);
338         if (status == 0 && filename) {
339             unlink(filename);
340             amfree(filename);
341         }
342         if (uncompress_err->len > 0) {
343             p_last = uncompress_err->pdata + uncompress_err->len;
344             for (p = uncompress_err->pdata; p < p_last ;p++) {
345                 g_ptr_array_add(*emsg, (char *)*p);
346             }
347         }
348         g_ptr_array_free(uncompress_err, TRUE);
349
350         status = get_pid_status(pid_index, "index", emsg);
351         if (status == 0 && filename) {
352             unlink(filename);
353             amfree(filename);
354         }
355
356         status = get_pid_status(pid_sort, SORT_PATH, emsg);
357         if (status == 0 && filename) {
358             unlink(filename);
359             amfree(filename);
360         }
361         if (sort_err->len > 0) {
362             p_last = sort_err->pdata + sort_err->len;
363             for (p = sort_err->pdata; p < p_last ;p++) {
364                 g_ptr_array_add(*emsg, (char *)*p);
365             }
366         }
367         g_ptr_array_free(sort_err, TRUE);
368
369         /* add at beginning */
370         if (filename) {
371             remove_file = (REMOVE_ITEM *)alloc(SIZEOF(REMOVE_ITEM));
372             remove_file->filename = stralloc(filename);
373             remove_file->next = uncompress_remove;
374             uncompress_remove = remove_file;
375         }
376
377     } else if(!S_ISREG((stat_filename.st_mode))) {
378             msg = vstrallocf(_("\"%s\" is not a regular file"), filename);
379             dbprintf("%s\n", msg);
380             g_ptr_array_add(*emsg, msg);
381             errno = -1;
382             amfree(filename);
383             amfree(cmd);
384             return NULL;
385     }
386     amfree(cmd);
387     return filename;
388 }
389
390 /* find all matching entries in a dump listing */
391 /* return -1 if error */
392 static int
393 process_ls_dump(
394     char *      dir,
395     DUMP_ITEM * dump_item,
396     int         recursive,
397     GPtrArray **emsg)
398 {
399     char line[STR_SIZE], old_line[STR_SIZE];
400     char *filename = NULL;
401     char *filename_gz;
402     char *dir_slash = NULL;
403     FILE *fp;
404     char *s;
405     int ch;
406     size_t len_dir_slash;
407
408     old_line[0] = '\0';
409     if (strcmp(dir, "/") == 0) {
410         dir_slash = stralloc(dir);
411     } else {
412         dir_slash = stralloc2(dir, "/");
413     }
414
415     filename_gz = get_index_name(dump_hostname, dump_item->hostname, disk_name,
416                                  dump_item->date, dump_item->level);
417     if (filename_gz == NULL) {
418         g_ptr_array_add(*emsg, stralloc(_("index file not found")));
419         amfree(filename_gz);
420         return -1;
421     }
422     filename = uncompress_file(filename_gz, emsg);
423     if(filename == NULL) {
424         amfree(filename_gz);
425         amfree(dir_slash);
426         return -1;
427     }
428     amfree(filename_gz);
429
430     if((fp = fopen(filename,"r"))==0) {
431         g_ptr_array_add(*emsg, vstrallocf("%s", strerror(errno)));
432         amfree(dir_slash);
433         amfree(filename);
434         return -1;
435     }
436
437     len_dir_slash=strlen(dir_slash);
438
439     while (fgets(line, STR_SIZE, fp) != NULL) {
440         if (line[0] != '\0') {
441             if(line[strlen(line)-1] == '\n')
442                 line[strlen(line)-1] = '\0';
443             if(strncmp(dir_slash, line, len_dir_slash) == 0) {
444                 if(!recursive) {
445                     s = line + len_dir_slash;
446                     ch = *s++;
447                     while(ch && ch != '/')
448                         ch = *s++;/* find end of the file name */
449                     if(ch == '/') {
450                         ch = *s++;
451                     }
452                     s[-1] = '\0';
453                 }
454                 if(strcmp(line, old_line) != 0) {
455                     add_dir_list_item(dump_item, line);
456                     strcpy(old_line, line);
457                 }
458             }
459         }
460     }
461     afclose(fp);
462     amfree(filename);
463     amfree(dir_slash);
464     return 0;
465 }
466
467 static void
468 reply_ptr_array(
469     int n,
470     GPtrArray *emsg)
471 {
472     gpointer *p;
473
474     if (emsg->len == 0)
475         return;
476
477     p = emsg->pdata;
478     while (p != emsg->pdata + emsg->len -1) {
479         fast_lreply(n, "%s", (char *)*p);
480         p++;
481     }
482     reply(n, "%s", (char *)*p);
483 }
484
485 /* send a 1 line reply to the client */
486 printf_arglist_function1(static void reply, int, n, char *, fmt)
487 {
488     va_list args;
489     int len;
490
491     if(!reply_buffer)
492         reply_buffer = alloc(reply_buffer_size);
493
494     while(1) {
495         arglist_start(args, fmt);
496         len = g_vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
497         arglist_end(args);
498
499         if (len > -1 && (size_t)len < reply_buffer_size-1)
500             break;
501
502         reply_buffer_size *= 2;
503         amfree(reply_buffer);
504         reply_buffer = alloc(reply_buffer_size);
505     }
506
507     if (g_fprintf(cmdout,"%03d %s\r\n", n, reply_buffer) < 0)
508     {
509         dbprintf(_("! error %d (%s) in printf\n"), errno, strerror(errno));
510         uncompress_remove = remove_files(uncompress_remove);
511         exit(1);
512     }
513     if (fflush(cmdout) != 0)
514     {
515         dbprintf(_("! error %d (%s) in fflush\n"), errno, strerror(errno));
516         uncompress_remove = remove_files(uncompress_remove);
517         exit(1);
518     }
519     dbprintf(_("< %03d %s\n"), n, reply_buffer);
520 }
521
522 /* send one line of a multi-line response */
523 printf_arglist_function1(static void lreply, int, n, char *, fmt)
524 {
525     va_list args;
526     int len;
527
528     if(!reply_buffer)
529         reply_buffer = alloc(reply_buffer_size);
530
531     while(1) {
532         arglist_start(args, fmt);
533         len = g_vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
534         arglist_end(args);
535
536         if (len > -1 && (size_t)len < reply_buffer_size-1)
537             break;
538
539         reply_buffer_size *= 2;
540         amfree(reply_buffer);
541         reply_buffer = alloc(reply_buffer_size);
542     }
543
544     if (g_fprintf(cmdout,"%03d-%s\r\n", n, reply_buffer) < 0)
545     {
546         dbprintf(_("! error %d (%s) in printf\n"),
547                   errno, strerror(errno));
548         uncompress_remove = remove_files(uncompress_remove);
549         exit(1);
550     }
551     if (fflush(cmdout) != 0)
552     {
553         dbprintf(_("! error %d (%s) in fflush\n"),
554                   errno, strerror(errno));
555         uncompress_remove = remove_files(uncompress_remove);
556         exit(1);
557     }
558
559     dbprintf("< %03d-%s\n", n, reply_buffer);
560
561 }
562
563 /* send one line of a multi-line response */
564 printf_arglist_function1(static void fast_lreply, int, n, char *, fmt)
565 {
566     va_list args;
567     int len;
568
569     if(!reply_buffer)
570         reply_buffer = alloc(reply_buffer_size);
571
572     while(1) {
573         arglist_start(args, fmt);
574         len = g_vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
575         arglist_end(args);
576
577         if (len > -1 && (size_t)len < reply_buffer_size-1)
578             break;
579
580         reply_buffer_size *= 2;
581         amfree(reply_buffer);
582         reply_buffer = alloc(reply_buffer_size);
583     }
584
585     if (g_fprintf(cmdout,"%03d-%s\r\n", n, reply_buffer) < 0)
586     {
587         dbprintf(_("! error %d (%s) in printf\n"),
588                   errno, strerror(errno));
589         uncompress_remove = remove_files(uncompress_remove);
590         exit(1);
591     }
592
593     DBG(2, "< %03d-%s", n, reply_buffer);
594 }
595
596 /* see if hostname is valid */
597 /* valid is defined to be that there is an index directory for it */
598 /* also do a security check on the requested dump hostname */
599 /* to restrict access to index records if required */
600 /* return -1 if not okay */
601 static am_host_t *
602 is_dump_host_valid(
603     char *      host)
604 {
605     am_host_t   *ihost;
606     disk_t      *diskp;
607
608     if (get_config_name() == NULL) {
609         reply(501, _("Must set config before setting host."));
610         return NULL;
611     }
612
613     /* check that the config actually handles that host */
614     ihost = lookup_host(host);
615     if(ihost == NULL) {
616         reply(501, _("Host %s is not in your disklist."), host);
617         return NULL;
618     }
619
620     /* check if an index dir exist */
621     if(get_index_dir(host, ihost->hostname, NULL)) {
622         return ihost;
623     }
624
625     /* check if an index dir exist for at least one DLE */
626     for(diskp = ihost->disks; diskp != NULL; diskp = diskp->hostnext) {
627         if (get_index_dir(diskp->hostname, NULL, NULL)) {
628             return ihost;
629         }
630     }
631
632     reply(501, _("No index records for host: %s. Have you enabled indexing?"),
633           host);
634     return NULL;
635 }
636
637
638 static gboolean
639 is_disk_allowed(
640     disk_t *disk)
641 {
642     dumptype_t *dt = disk->config;
643     recovery_limit_t *rl = NULL;
644     char *peer;
645     char *dle_hostname;
646     GSList *iter;
647
648     /* get the config: either for the DLE or the global config */
649     if (dt) {
650         if (dumptype_seen(dt, DUMPTYPE_RECOVERY_LIMIT)) {
651             g_debug("using recovery limit from DLE");
652             rl = dumptype_get_recovery_limit(dt);
653         }
654     }
655     if (!rl) {
656         if (getconf_seen(CNF_RECOVERY_LIMIT)) {
657             g_debug("using global recovery limit");
658             rl = getconf_recovery_limit(CNF_RECOVERY_LIMIT);
659         }
660     }
661     if (!rl) {
662         g_debug("no recovery limit found; allowing access");
663         return TRUE;
664     }
665
666     peer = getenv("AMANDA_AUTHENTICATED_PEER");
667     if (!peer || !*peer) {
668         g_warning("DLE has a recovery-limit, but no authenticated peer name is "
669                   "available; rejecting");
670         return FALSE;
671     }
672
673     /* check same-host */
674     dle_hostname = disk->host? disk->host->hostname : NULL;
675     if (rl->same_host && dle_hostname) {
676         if (0 == g_ascii_strcasecmp(peer, dle_hostname)) {
677             g_debug("peer matched same-host ('%s')", dle_hostname);
678             return TRUE;
679         }
680     }
681
682     /* check the match list */
683     for (iter = rl->match_pats; iter; iter = iter->next) {
684         char *pat = iter->data;
685         if (match_host(pat, peer))
686             return TRUE;
687     }
688
689     g_warning("peer '%s' does not match any of the recovery-limit restrictions; rejecting",
690             peer);
691
692     return FALSE;
693 }
694
695 static int
696 is_disk_valid(
697     char *disk)
698 {
699     disk_t *idisk;
700     char *qdisk;
701
702     if (get_config_name() == NULL) {
703         reply(501, _("Must set config,host before setting disk."));
704         return -1;
705     }
706     else if (dump_hostname == NULL) {
707         reply(501, _("Must set host before setting disk."));
708         return -1;
709     }
710
711     /* check that the config actually handles that disk, and that recovery-limit allows it */
712     idisk = lookup_disk(dump_hostname, disk);
713     if(idisk == NULL || !is_disk_allowed(idisk)) {
714         qdisk = quote_string(disk);
715         reply(501, _("Disk %s:%s is not in the server's disklist."), dump_hostname, qdisk);
716         amfree(qdisk);
717         return -1;
718     }
719
720     /* assume an index dir already */
721     if (get_index_dir(dump_hostname, idisk->hostname, disk) == 0) {
722         qdisk = quote_string(disk);
723         reply(501, _("No index records for disk: %s. Invalid?"), qdisk);
724         amfree(qdisk);
725         return -1;
726     }
727
728     return 0;
729 }
730
731
732 static int
733 check_and_load_config(
734     char *      config)
735 {
736     char *conf_diskfile;
737     char *conf_tapelist;
738     char *conf_indexdir;
739     struct stat dir_stat;
740
741     /* check that the config actually exists */
742     if (config == NULL) {
743         reply(501, _("Must set config first."));
744         return -1;
745     }
746
747     /* (re-)initialize configuration with the new config name */
748     config_init(CONFIG_INIT_EXPLICIT_NAME, config);
749     if (config_errors(NULL) >= CFGERR_ERRORS) {
750         reply(501, _("Could not read config file for %s!"), config);
751         return -1;
752     }
753
754     check_running_as(RUNNING_AS_DUMPUSER_PREFERRED);
755
756     conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
757     read_diskfile(conf_diskfile, &disk_list);
758     amfree(conf_diskfile);
759     if (config_errors(NULL) >= CFGERR_ERRORS) {
760         reply(501, _("Could not read disk file %s!"), conf_diskfile);
761         return -1;
762     }
763
764     conf_tapelist = config_dir_relative(getconf_str(CNF_TAPELIST));
765     if(read_tapelist(conf_tapelist)) {
766         reply(501, _("Could not read tapelist file %s!"), conf_tapelist);
767         amfree(conf_tapelist);
768         return -1;
769     }
770     amfree(conf_tapelist);
771
772     dbrename(get_config_name(), DBG_SUBDIR_SERVER);
773
774     output_find = find_dump(&disk_list);
775     /* the 'w' here sorts by write timestamp, so that the first instance of
776      * any particular datestamp/host/disk/level/part that we see is the one
777      * written earlier */
778     sort_find_result("DLKHpwB", &output_find);
779
780     conf_indexdir = config_dir_relative(getconf_str(CNF_INDEXDIR));
781     if (stat (conf_indexdir, &dir_stat) != 0 || !S_ISDIR(dir_stat.st_mode)) {
782         reply(501, _("Index directory %s does not exist"), conf_indexdir);
783         amfree(conf_indexdir);
784         return -1;
785     }
786     amfree(conf_indexdir);
787
788     return 0;
789 }
790
791
792 static int
793 build_disk_table(void)
794 {
795     char *date;
796     char *last_timestamp;
797     off_t last_filenum;
798     int last_level;
799     int last_partnum;
800     find_result_t *find_output;
801
802     if (get_config_name() == NULL) {
803         reply(590, _("Must set config,host,disk before building disk table"));
804         return -1;
805     }
806     else if (dump_hostname == NULL) {
807         reply(590, _("Must set host,disk before building disk table"));
808         return -1;
809     }
810     else if (disk_name == NULL) {
811         reply(590, _("Must set disk before building disk table"));
812         return -1;
813     }
814
815     clear_list();
816     last_timestamp = NULL;
817     last_filenum = (off_t)-1;
818     last_level = -1;
819     last_partnum = -1;
820     for(find_output = output_find;
821         find_output != NULL; 
822         find_output = find_output->next) {
823         if(strcasecmp(dump_hostname, find_output->hostname) == 0 &&
824            strcmp(disk_name    , find_output->diskname)     == 0 &&
825            strcmp("OK"         , find_output->status)       == 0 &&
826            strcmp("OK"         , find_output->dump_status)  == 0) {
827             /*
828              * The sort order puts holding disk entries first.  We want to
829              * use them if at all possible, so ignore any other entries
830              * for the same datestamp after we see a holding disk entry
831              * (as indicated by a filenum of zero).
832              */
833             if(last_timestamp &&
834                strcmp(find_output->timestamp, last_timestamp) == 0 &&
835                find_output->level == last_level && 
836                last_filenum == 0) {
837                 continue;
838             }
839             /* ignore duplicate partnum */
840             if(last_timestamp &&
841                strcmp(find_output->timestamp, last_timestamp) == 0 &&
842                find_output->level == last_level && 
843                find_output->partnum == last_partnum) {
844                 continue;
845             }
846             last_timestamp = find_output->timestamp;
847             last_filenum = find_output->filenum;
848             last_level = find_output->level;
849             last_partnum = find_output->partnum;
850             date = amindexd_nicedate(find_output->timestamp);
851             add_dump(find_output->hostname, date, find_output->level,
852                      find_output->label, find_output->filenum,
853                      find_output->partnum, find_output->totalparts);
854             dbprintf("- %s %d %s %lld %d %d\n",
855                      date, find_output->level, 
856                      find_output->label,
857                      (long long)find_output->filenum,
858                      find_output->partnum, find_output->totalparts);
859         }
860     }
861
862     clean_dump();
863
864     return 0;
865 }
866
867
868 static int
869 disk_history_list(void)
870 {
871     DUMP_ITEM *item;
872     char date[20];
873
874     if (get_config_name() == NULL) {
875         reply(502, _("Must set config,host,disk before listing history"));
876         return -1;
877     }
878     else if (dump_hostname == NULL) {
879         reply(502, _("Must set host,disk before listing history"));
880         return -1;
881     }
882     else if (disk_name == NULL) {
883         reply(502, _("Must set disk before listing history"));
884         return -1;
885     }
886
887     lreply(200, _(" Dump history for config \"%s\" host \"%s\" disk %s"),
888           get_config_name(), dump_hostname, qdisk_name);
889
890     for (item=first_dump(); item!=NULL; item=next_dump(item)){
891         char *tapelist_str = marshal_tapelist(item->tapes, 1);
892
893         strncpy(date, item->date, 20);
894         date[19] = '\0';
895         if(!am_has_feature(their_features,fe_amrecover_timestamp))
896             date[10] = '\0';
897
898         if(am_has_feature(their_features, fe_amindexd_marshall_in_DHST)){
899             lreply(201, " %s %d %s", date, item->level, tapelist_str);
900         }
901         else{
902             lreply(201, " %s %d %s %lld", date, item->level,
903                 tapelist_str, (long long)item->file);
904         }
905         amfree(tapelist_str);
906     }
907
908     reply(200, _("Dump history for config \"%s\" host \"%s\" disk %s"),
909           get_config_name(), dump_hostname, qdisk_name);
910
911     return 0;
912 }
913
914
915 /*
916  * is the directory dir backed up - dir assumed complete relative to
917  * disk mount point
918  */
919 /* opaque version of command */
920 static int
921 is_dir_valid_opaque(
922     char *dir)
923 {
924     DUMP_ITEM *item;
925     char line[STR_SIZE];
926     FILE *fp;
927     int last_level;
928     char *ldir = NULL;
929     char *filename_gz = NULL;
930     char *filename = NULL;
931     size_t ldir_len;
932     GPtrArray *emsg = NULL;
933
934     if (get_config_name() == NULL || dump_hostname == NULL || disk_name == NULL) {
935         reply(502, _("Must set config,host,disk before asking about directories"));
936         return -1;
937     }
938     else if (dump_hostname == NULL) {
939         reply(502, _("Must set host,disk before asking about directories"));
940         return -1;
941     }
942     else if (disk_name == NULL) {
943         reply(502, _("Must set disk before asking about directories"));
944         return -1;
945     }
946     else if (target_date == NULL) {
947         reply(502, _("Must set date before asking about directories"));
948         return -1;
949     }
950     /* scan through till we find first dump on or before date */
951     for (item=first_dump(); item!=NULL; item=next_dump(item))
952         if (cmp_date(item->date, target_date) <= 0)
953             break;
954
955     if (item == NULL)
956     {
957         /* no dump for given date */
958         reply(500, _("No dumps available on or before date \"%s\""), target_date);
959         return -1;
960     }
961
962     if(strcmp(dir, "/") == 0) {
963         ldir = stralloc(dir);
964     } else {
965         ldir = stralloc2(dir, "/");
966     }
967     ldir_len = strlen(ldir);
968
969     /* go back till we hit a level 0 dump */
970     do
971     {
972         amfree(filename);
973         filename_gz = get_index_name(dump_hostname, item->hostname, disk_name,
974                                      item->date, item->level);
975         if (filename_gz == NULL) {
976             reply(599, "index not found");
977             amfree(ldir);
978             return -1;
979         }
980         emsg = g_ptr_array_new();
981         if((filename = uncompress_file(filename_gz, &emsg)) == NULL) {
982             reply_ptr_array(599, emsg);
983             amfree(filename_gz);
984             g_ptr_array_free_full(emsg);
985             amfree(ldir);
986             return -1;
987         }
988         g_ptr_array_free_full(emsg);
989         amfree(filename_gz);
990         dbprintf("f %s\n", filename);
991         if ((fp = fopen(filename, "r")) == NULL) {
992             reply(599, _("System error: %s"), strerror(errno));
993             amfree(filename);
994             amfree(ldir);
995             return -1;
996         }
997         while (fgets(line, STR_SIZE, fp) != NULL) {
998             if (line[0] == '\0')
999                 continue;
1000             if(line[strlen(line)-1] == '\n')
1001                 line[strlen(line)-1] = '\0';
1002             if (strncmp(line, ldir, ldir_len) != 0) {
1003                 continue;                       /* not found yet */
1004             }
1005             amfree(filename);
1006             amfree(ldir);
1007             afclose(fp);
1008             return 0;
1009         }
1010         afclose(fp);
1011
1012         last_level = item->level;
1013         do
1014         {
1015             item=next_dump(item);
1016         } while ((item != NULL) && (item->level >= last_level));
1017     } while (item != NULL);
1018
1019     amfree(filename);
1020     amfree(ldir);
1021     reply(500, _("\"%s\" is an invalid directory"), dir);
1022     return -1;
1023 }
1024
1025 static int
1026 opaque_ls(
1027     char *      dir,
1028     int         recursive)
1029 {
1030     DUMP_ITEM *dump_item;
1031     DIR_ITEM *dir_item;
1032     int level, last_level;
1033     GPtrArray *emsg = NULL;
1034     am_feature_e marshall_feature;
1035
1036     if (recursive) {
1037         marshall_feature = fe_amindexd_marshall_in_ORLD;
1038     } else {
1039         marshall_feature = fe_amindexd_marshall_in_OLSD;
1040     }
1041
1042     clear_dir_list();
1043
1044     if (get_config_name() == NULL) {
1045         reply(502, _("Must set config,host,disk before listing a directory"));
1046         return -1;
1047     }
1048     else if (dump_hostname == NULL) {
1049         reply(502, _("Must set host,disk before listing a directory"));
1050         return -1;
1051     }
1052     else if (disk_name == NULL) {
1053         reply(502, _("Must set disk before listing a directory"));
1054         return -1;
1055     }
1056     else if (target_date == NULL) {
1057         reply(502, _("Must set date before listing a directory"));
1058         return -1;
1059     }
1060
1061     /* scan through till we find first dump on or before date */
1062     for (dump_item=first_dump(); dump_item!=NULL; dump_item=next_dump(dump_item))
1063         if (cmp_date(dump_item->date, target_date) <= 0)
1064             break;
1065
1066     if (dump_item == NULL)
1067     {
1068         /* no dump for given date */
1069         reply(500, _("No dumps available on or before date \"%s\""), target_date);
1070         return -1;
1071     }
1072
1073     /* get data from that dump */
1074     emsg = g_ptr_array_new();
1075     if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
1076         reply_ptr_array(599, emsg);
1077         g_ptr_array_free_full(emsg);
1078         return -1;
1079     }
1080
1081     /* go back processing higher level dumps till we hit a level 0 dump */
1082     last_level = dump_item->level;
1083     while ((last_level != 0) && ((dump_item=next_dump(dump_item)) != NULL))
1084     {
1085         if (dump_item->level < last_level)
1086         {
1087             last_level = dump_item->level;
1088             if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
1089                 reply_ptr_array(599, emsg);
1090                 g_ptr_array_free_full(emsg);
1091                 return -1;
1092             }
1093         }
1094     }
1095     g_ptr_array_free_full(emsg);
1096
1097     /* return the information to the caller */
1098     lreply(200, _(" Opaque list of %s"), dir);
1099     for(level=0; level < DUMP_LEVELS; level++) {
1100         for (dir_item = get_dir_list(); dir_item != NULL; 
1101              dir_item = dir_item->next) {
1102
1103             if(dir_item->dump->level == level) {
1104                 if (!am_has_feature(their_features, marshall_feature) &&
1105                     (num_entries(dir_item->dump->tapes) > 1 ||
1106                     dir_item->dump->tapes->numfiles > 1)) {
1107                     fast_lreply(501, _(" ERROR: Split dumps not supported"
1108                                 " with old version of amrecover."));
1109                     break;
1110                 }
1111                 else {
1112                     opaque_ls_one(dir_item, marshall_feature, recursive);
1113                 }
1114             }
1115         }
1116     }
1117     reply(200, _(" Opaque list of %s"), dir);
1118
1119     clear_dir_list();
1120     return 0;
1121 }
1122
1123 void opaque_ls_one(
1124     DIR_ITEM *   dir_item,
1125     am_feature_e marshall_feature,
1126     int          recursive)
1127 {
1128     char date[20];
1129     char *tapelist_str;
1130     char *qtapelist_str;
1131     char *qpath;
1132
1133     if (am_has_feature(their_features, marshall_feature)) {
1134         tapelist_str = marshal_tapelist(dir_item->dump->tapes, 1);
1135     } else {
1136         tapelist_str = dir_item->dump->tapes->label;
1137     }
1138
1139     if (am_has_feature(their_features, fe_amindexd_quote_label)) {
1140         qtapelist_str = quote_string(tapelist_str);
1141     } else {
1142         qtapelist_str = stralloc(tapelist_str);
1143     }
1144     strncpy(date, dir_item->dump->date, 20);
1145     date[19] = '\0';
1146     if(!am_has_feature(their_features,fe_amrecover_timestamp))
1147         date[10] = '\0';
1148
1149     qpath = quote_string(dir_item->path);
1150     if((!recursive && am_has_feature(their_features,
1151                                      fe_amindexd_fileno_in_OLSD)) ||
1152        (recursive && am_has_feature(their_features,
1153                                     fe_amindexd_fileno_in_ORLD))) {
1154         fast_lreply(201, " %s %d %s %lld %s",
1155                     date,
1156                     dir_item->dump->level,
1157                     qtapelist_str,
1158                     (long long)dir_item->dump->file,
1159                     qpath);
1160     }
1161     else {
1162
1163         fast_lreply(201, " %s %d %s %s",
1164                     date, dir_item->dump->level,
1165                     qtapelist_str, qpath);
1166     }
1167     amfree(qpath);
1168     if(am_has_feature(their_features, marshall_feature)) {
1169         amfree(tapelist_str);
1170     }
1171     amfree(qtapelist_str);
1172 }
1173
1174 /*
1175  * returns the value of changer or tapedev from the amanda.conf file if set,
1176  * otherwise reports an error
1177  */
1178
1179 static int
1180 tapedev_is(void)
1181 {
1182     char *result;
1183
1184     /* check state okay to do this */
1185     if (get_config_name() == NULL) {
1186         reply(501, _("Must set config before asking about tapedev."));
1187         return -1;
1188     }
1189
1190     /* use amrecover_changer if possible */
1191     if ((result = getconf_str(CNF_AMRECOVER_CHANGER)) != NULL  &&
1192         *result != '\0') {
1193         dbprintf(_("tapedev_is amrecover_changer: %s\n"), result);
1194         reply(200, "%s", result);
1195         return 0;
1196     }
1197
1198     /* use changer if possible */
1199     if ((result = getconf_str(CNF_TPCHANGER)) != NULL  &&  *result != '\0') {
1200         dbprintf(_("tapedev_is tpchanger: %s\n"), result);
1201         reply(200, "%s", result);
1202         return 0;
1203     }
1204
1205     /* get tapedev value */
1206     if ((result = getconf_str(CNF_TAPEDEV)) != NULL  &&  *result != '\0') {
1207         dbprintf(_("tapedev_is tapedev: %s\n"), result);
1208         reply(200, "%s", result);
1209         return 0;
1210     }
1211
1212     dbprintf(_("No tapedev or tpchanger in config site.\n"));
1213
1214     reply(501, _("Tapedev or tpchanger not set in config file."));
1215     return -1;
1216 }
1217
1218
1219 /* returns YES if dumps for disk are compressed, NO if not */
1220 static int
1221 are_dumps_compressed(void)
1222 {
1223     disk_t *diskp;
1224
1225     /* check state okay to do this */
1226     if (get_config_name() == NULL) {
1227         reply(501, _("Must set config,host,disk name before asking about dumps."));
1228         return -1;
1229     }
1230     else if (dump_hostname == NULL) {
1231         reply(501, _("Must set host,disk name before asking about dumps."));
1232         return -1;
1233     }
1234     else if (disk_name == NULL) {
1235         reply(501, _("Must set disk name before asking about dumps."));
1236         return -1;
1237     }
1238
1239     /* now go through the list of disks and find which have indexes */
1240     for (diskp = disk_list.head; diskp != NULL; diskp = diskp->next) {
1241         if ((strcasecmp(diskp->host->hostname, dump_hostname) == 0)
1242                 && (strcmp(diskp->name, disk_name) == 0)) {
1243             break;
1244         }
1245     }
1246
1247     if (diskp == NULL) {
1248         reply(501, _("Couldn't find host/disk in disk file."));
1249         return -1;
1250     }
1251
1252     /* send data to caller */
1253     if (diskp->compress == COMP_NONE)
1254         reply(200, "NO");
1255     else
1256         reply(200, "YES");
1257
1258     return 0;
1259 }
1260
1261 int
1262 main(
1263     int         argc,
1264     char **     argv)
1265 {
1266     char *line = NULL, *part = NULL;
1267     char *s;
1268     int ch;
1269     char *cmd_undo, cmd_undo_ch;
1270     socklen_t_equiv socklen;
1271     sockaddr_union his_addr;
1272     char *arg = NULL;
1273     char *cmd;
1274     size_t len;
1275     int user_validated = 0;
1276     char *errstr = NULL;
1277     char *pgm = "amindexd";             /* in case argv[0] is not set */
1278     char his_hostname[MAX_HOSTNAME_LENGTH];
1279     char *cfg_opt = NULL;
1280
1281     /*
1282      * Configure program for internationalization:
1283      *   1) Only set the message locale for now.
1284      *   2) Set textdomain for all amanda related programs to "amanda"
1285      *      We don't want to be forced to support dozens of message catalogs.
1286      */  
1287     setlocale(LC_MESSAGES, "C");
1288     textdomain("amanda"); 
1289
1290     safe_fd(DATA_FD_OFFSET, 2);
1291     openbsd_fd_inform();
1292     safe_cd();
1293
1294     /*
1295      * When called via inetd, it is not uncommon to forget to put the
1296      * argv[0] value on the config line.  On some systems (e.g. Solaris)
1297      * this causes argv and/or argv[0] to be NULL, so we have to be
1298      * careful getting our name.
1299      */
1300     if (argc >= 1 && argv != NULL && argv[0] != NULL) {
1301         if((pgm = strrchr(argv[0], '/')) != NULL) {
1302             pgm++;
1303         } else {
1304             pgm = argv[0];
1305         }
1306     }
1307
1308     set_pname(pgm);
1309
1310     /* Don't die when child closes pipe */
1311     signal(SIGPIPE, SIG_IGN);
1312
1313     dbopen(DBG_SUBDIR_SERVER);
1314     dbprintf(_("version %s\n"), VERSION);
1315
1316     if(argv == NULL) {
1317         error("argv == NULL\n");
1318     }
1319
1320     if (! (argc >= 1 && argv[0] != NULL)) {
1321         dbprintf(_("WARNING: argv[0] not defined: check inetd.conf\n"));
1322     }
1323
1324     debug_dup_stderr_to_debug();
1325
1326     /* initialize */
1327
1328     argc--;
1329     argv++;
1330
1331     if(argc > 0 && strcmp(*argv, "-t") == 0) {
1332         amindexd_debug = 1;
1333         argc--;
1334         argv++;
1335     }
1336
1337     if(argc > 0 && strcmp(*argv, "amandad") == 0) {
1338         from_amandad = 1;
1339         argc--;
1340         argv++;
1341         if(argc > 0) {
1342             amandad_auth = *argv;
1343             argc--;
1344             argv++;
1345         }
1346     }
1347     else {
1348         from_amandad = 0;
1349         safe_fd(dbfd(), 1);
1350     }
1351
1352     if (argc > 0) {
1353         cfg_opt = *argv;
1354         argc--;
1355         argv++;
1356     }
1357
1358     if(gethostname(local_hostname, SIZEOF(local_hostname)-1) == -1) {
1359         error(_("gethostname: %s"), strerror(errno));
1360         /*NOTREACHED*/
1361     }
1362     local_hostname[SIZEOF(local_hostname)-1] = '\0';
1363
1364     /* now trim domain off name */
1365     s = local_hostname;
1366     ch = *s++;
1367     while(ch && ch != '.') ch = *s++;
1368     s[-1] = '\0';
1369
1370
1371     if(from_amandad == 0) {
1372         if(!amindexd_debug) {
1373             /* who are we talking to? */
1374             socklen = sizeof (his_addr);
1375             if (getpeername(0, (struct sockaddr *)&his_addr, &socklen) == -1)
1376                 error(_("getpeername: %s"), strerror(errno));
1377
1378             /* Try a reverse (IP->hostname) resolution, and fail if it does
1379              * not work -- this is a basic security check */
1380             if (getnameinfo((struct sockaddr *)&his_addr, SS_LEN(&his_addr),
1381                             his_hostname, sizeof(his_hostname),
1382                             NULL, 0,
1383                             0)) {
1384                 error(_("getnameinfo(%s): hostname lookup failed"),
1385                       str_sockaddr(&his_addr));
1386                 /*NOTREACHED*/
1387             }
1388         }
1389
1390         /* Set up the input and output FILEs */
1391         cmdout = stdout;
1392         cmdin = stdin;
1393     }
1394     else {
1395         /* read the REQ packet */
1396         for(; (line = agets(stdin)) != NULL; free(line)) {
1397             if(strncmp_const(line, "OPTIONS ") == 0) {
1398                 if (g_options != NULL) {
1399                     dbprintf(_("REQ packet specified multiple OPTIONS.\n"));
1400                     free_g_options(g_options);
1401                 }
1402                 g_options = parse_g_options(line+8, 1);
1403                 if(!g_options->hostname) {
1404                     g_options->hostname = alloc(MAX_HOSTNAME_LENGTH+1);
1405                     gethostname(g_options->hostname, MAX_HOSTNAME_LENGTH);
1406                     g_options->hostname[MAX_HOSTNAME_LENGTH] = '\0';
1407                 }
1408             }
1409         }
1410         amfree(line);
1411
1412         if(amandad_auth && g_options->auth) {
1413             if(strcasecmp(amandad_auth, g_options->auth) != 0) {
1414                 g_printf(_("ERROR recover program ask for auth=%s while amindexd is configured for '%s'\n"),
1415                        g_options->auth, amandad_auth);
1416                 error(_("amindexd: ERROR recover program ask for auth=%s while amindexd is configured for '%s'"),
1417                       g_options->auth, amandad_auth);
1418                 /*NOTREACHED*/
1419             }
1420         }
1421         /* send the REP packet */
1422         g_printf("CONNECT MESG %d\n", DATA_FD_OFFSET);
1423         g_printf("\n");
1424         fflush(stdin);
1425         fflush(stdout);
1426         fclose(stdin);
1427         fclose(stdout);
1428         
1429         cmdout = fdopen(DATA_FD_OFFSET + 0, "a");
1430         if (!cmdout) {
1431             error(_("amindexd: Can't fdopen(%d): %s"), DATA_FD_OFFSET + 0, strerror(errno));
1432             /*NOTREACHED*/
1433         }
1434
1435         cmdin = fdopen(DATA_FD_OFFSET + 1, "r");
1436         if (!cmdin) {
1437             error(_("amindexd: Can't fdopen(%d): %s"), DATA_FD_OFFSET + 1, strerror(errno));
1438             /*NOTREACHED*/
1439         }
1440     }
1441
1442     /* clear these so we can detect when the have not been set by the client */
1443     amfree(dump_hostname);
1444     amfree(qdisk_name);
1445     amfree(disk_name);
1446     amfree(target_date);
1447
1448     our_features = am_init_feature_set();
1449     their_features = am_set_default_feature_set();
1450
1451     if (cfg_opt != NULL && check_and_load_config(cfg_opt) != -1) { /* load the config */
1452         return 1;
1453     }
1454
1455     reply(220, _("%s AMANDA index server (%s) ready."), local_hostname,
1456           VERSION);
1457
1458     user_validated = from_amandad;
1459
1460     /* a real simple parser since there are only a few commands */
1461     while (1)
1462     {
1463         /* get a line from the client */
1464         while(1) {
1465             if((part = agets(cmdin)) == NULL) {
1466                 if(errno != 0) {
1467                     dbprintf(_("? read error: %s\n"), strerror(errno));
1468                 } else {
1469                     dbprintf(_("? unexpected EOF\n"));
1470                 }
1471                 if(line) {
1472                     dbprintf(_("? unprocessed input:\n"));
1473                     dbprintf("-----\n");
1474                     dbprintf("? %s\n", line);
1475                     dbprintf("-----\n");
1476                 }
1477                 amfree(line);
1478                 amfree(part);
1479                 uncompress_remove = remove_files(uncompress_remove);
1480                 dbclose();
1481                 return 1;               /* they hung up? */
1482             }
1483             strappend(line, part);      /* Macro: line can be null */
1484             amfree(part);
1485
1486             if(amindexd_debug) {
1487                 break;                  /* we have a whole line */
1488             }
1489             if((len = strlen(line)) > 0 && line[len-1] == '\r') {
1490                 line[len-1] = '\0';     /* zap the '\r' */
1491                 break;
1492             }
1493             /*
1494              * Hmmm.  We got a "line" from agets(), which means it saw
1495              * a '\n' (or EOF, etc), but there was not a '\r' before it.
1496              * Put a '\n' back in the buffer and loop for more.
1497              */
1498             strappend(line, "\n");
1499         }
1500
1501         dbprintf("> %s\n", line);
1502
1503         if (arg != NULL)
1504             amfree(arg);
1505         s = line;
1506         ch = *s++;
1507
1508         skip_whitespace(s, ch);
1509         if(ch == '\0') {
1510             reply(500, _("Command not recognised/incorrect: %s"), line);
1511             amfree(line);
1512             continue;
1513         }
1514         cmd = s - 1;
1515
1516         skip_non_whitespace(s, ch);
1517         cmd_undo = s-1;                         /* for error message */
1518         cmd_undo_ch = *cmd_undo;
1519         *cmd_undo = '\0';
1520         if (ch) {
1521             skip_whitespace(s, ch);             /* find the argument */
1522             if (ch) {
1523                 arg = s-1;
1524                 skip_quoted_string(s, ch);
1525                 arg = unquote_string(arg);
1526             }
1527         }
1528
1529         amfree(errstr);
1530         if (!user_validated && strcmp(cmd, "SECURITY") == 0 && arg) {
1531             user_validated = amindexd_debug ||
1532                                 check_security(
1533                                         (sockaddr_union *)&his_addr,
1534                                         arg, 0, &errstr);
1535             if(user_validated) {
1536                 reply(200, _("Access OK"));
1537                 amfree(line);
1538                 continue;
1539             }
1540         }
1541         if (!user_validated) {  /* don't tell client the reason, just log it to debug log */
1542             reply(500, _("Access not allowed"));
1543             if (errstr) {   
1544                 dbprintf("%s\n", errstr);
1545             }
1546             break;
1547         }
1548
1549         if (strcmp(cmd, "QUIT") == 0) {
1550             amfree(line);
1551             break;
1552         } else if (strcmp(cmd, "HOST") == 0 && arg) {
1553             am_host_t *lhost;
1554             /* set host we are restoring */
1555             s[-1] = '\0';
1556             if ((lhost = is_dump_host_valid(arg)) != NULL)
1557             {
1558                 dump_hostname = newstralloc(dump_hostname, lhost->hostname);
1559                 reply(200, _("Dump host set to %s."), dump_hostname);
1560                 amfree(qdisk_name);             /* invalidate any value */
1561                 amfree(disk_name);              /* invalidate any value */
1562             }
1563             s[-1] = (char)ch;
1564         } else if (strcmp(cmd, "LISTHOST") == 0) {
1565             disk_t *disk, 
1566                    *diskdup;
1567             int nbhost = 0,
1568                 found = 0;
1569             s[-1] = '\0';
1570             if (get_config_name() == NULL) {
1571                 reply(501, _("Must set config before listhost"));
1572             }
1573             else {
1574                 lreply(200, _(" List hosts for config %s"), get_config_name());
1575                 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1576                     found = 0;
1577                     for (diskdup = disk_list.head; diskdup!=disk; diskdup = diskdup->next) {
1578                         if(strcmp(diskdup->host->hostname, disk->host->hostname) == 0) {
1579                           found = 1;
1580                           break;
1581                         }
1582                     }
1583                     if(!found){
1584                         fast_lreply(201, " %s", disk->host->hostname);
1585                         nbhost++;
1586                     }
1587                 }
1588                 if(nbhost > 0) {
1589                     reply(200, _(" List hosts for config %s"), get_config_name());
1590                 }
1591                 else {
1592                     reply(200, _("No hosts for config %s"), get_config_name());
1593                 }
1594             }
1595             s[-1] = (char)ch;
1596         } else if (strcmp(cmd, "DISK") == 0 && arg) {
1597             s[-1] = '\0';
1598             if (is_disk_valid(arg) != -1) {
1599                 disk_name = newstralloc(disk_name, arg);
1600                 qdisk_name = quote_string(disk_name);
1601                 if (build_disk_table() != -1) {
1602                     reply(200, _("Disk set to %s."), qdisk_name);
1603                 }
1604             }
1605             s[-1] = (char)ch;
1606         } else if (strcmp(cmd, "DLE") == 0) {
1607             disk_t *dp;
1608             char *optionstr;
1609             char *b64disk;
1610             char *l, *ql;
1611
1612             dp = lookup_disk(dump_hostname, disk_name);
1613             if (dp->line == 0) {
1614                 reply(200, "NODLE");
1615             } else {
1616                 GPtrArray *errarray;
1617                 guint      i;
1618
1619                 b64disk = amxml_format_tag("disk", dp->name);
1620                 dp->host->features = their_features;
1621                 errarray = validate_optionstr(dp);
1622                 if (errarray->len > 0) {
1623                     for (i=0; i < errarray->len; i++) {
1624                         g_debug(_("ERROR: %s:%s %s"),
1625                                 dump_hostname, disk_name,
1626                                 (char *)g_ptr_array_index(errarray, i));
1627                     }
1628                     g_ptr_array_free(errarray, TRUE);
1629                     reply(200, "NODLE");
1630                 } else {
1631                     optionstr = xml_optionstr(dp, 0);
1632                     l = vstralloc("<dle>\n",
1633                               "  <program>", dp->program, "</program>\n", NULL);
1634                     if (dp->application) {
1635                         application_t *application;
1636                         char *xml_app;
1637
1638                         application = lookup_application(dp->application);
1639                         g_assert(application != NULL);
1640                         xml_app = xml_application(dp, application,
1641                                                   their_features);
1642                         vstrextend(&l, xml_app, NULL);
1643                         amfree(xml_app);
1644                     }
1645                     vstrextend(&l, "  ", b64disk, "\n", NULL);
1646                     if (dp->device) {
1647                         char *b64device = amxml_format_tag("diskdevice",
1648                                                            dp->device);
1649                         vstrextend(&l, "  ", b64device, "\n", NULL);
1650                         amfree(b64device);
1651                     }
1652                     vstrextend(&l, optionstr, "</dle>\n", NULL);
1653                     ql = quote_string(l);
1654                     reply(200, "%s", ql);
1655                     amfree(optionstr);
1656                     amfree(l);
1657                     amfree(ql);
1658                     amfree(b64disk);
1659                 }
1660             }
1661         } else if (strcmp(cmd, "LISTDISK") == 0) {
1662             char *qname;
1663             disk_t *disk;
1664             int nbdisk = 0;
1665             s[-1] = '\0';
1666             if (get_config_name() == NULL) {
1667                 reply(501, _("Must set config, host before listdisk"));
1668             }
1669             else if (dump_hostname == NULL) {
1670                 reply(501, _("Must set host before listdisk"));
1671             }
1672             else if(arg) {
1673                 lreply(200, _(" List of disk for device %s on host %s"), arg,
1674                        dump_hostname);
1675                 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1676
1677                     if (strcasecmp(disk->host->hostname, dump_hostname) == 0 &&
1678                       ((disk->device && strcmp(disk->device, arg) == 0) ||
1679                       (!disk->device && strcmp(disk->name, arg) == 0))) {
1680                         qname = quote_string(disk->name);
1681                         fast_lreply(201, " %s", qname);
1682                         amfree(qname);
1683                         nbdisk++;
1684                     }
1685                 }
1686                 if(nbdisk > 0) {
1687                     reply(200, _("List of disk for device %s on host %s"), arg,
1688                           dump_hostname);
1689                 }
1690                 else {
1691                     reply(200, _("No disk for device %s on host %s"), arg,
1692                           dump_hostname);
1693                 }
1694             }
1695             else {
1696                 lreply(200, _(" List of disk for host %s"), dump_hostname);
1697                 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1698                     if(strcasecmp(disk->host->hostname, dump_hostname) == 0) {
1699                         qname = quote_string(disk->name);
1700                         fast_lreply(201, " %s", qname);
1701                         amfree(qname);
1702                         nbdisk++;
1703                     }
1704                 }
1705                 if(nbdisk > 0) {
1706                     reply(200, _("List of disk for host %s"), dump_hostname);
1707                 }
1708                 else {
1709                     reply(200, _("No disk for host %s"), dump_hostname);
1710                 }
1711             }
1712             s[-1] = (char)ch;
1713         } else if (strcmp(cmd, "SCNF") == 0 && arg) {
1714             s[-1] = '\0';
1715             if (check_and_load_config(arg) != -1) {    /* try to load the new config */
1716                 amfree(dump_hostname);          /* invalidate any value */
1717                 amfree(qdisk_name);             /* invalidate any value */
1718                 amfree(disk_name);              /* invalidate any value */
1719                 reply(200, _("Config set to %s."), get_config_name());
1720             } /* check_and_load_config replies with any failure messages */
1721             s[-1] = (char)ch;
1722         } else if (strcmp(cmd, "FEATURES") == 0 && arg) {
1723             char *our_feature_string = NULL;
1724             char *their_feature_string = NULL;
1725             s[-1] = '\0';
1726             am_release_feature_set(our_features);
1727             am_release_feature_set(their_features);
1728             our_features = am_init_feature_set();
1729             our_feature_string = am_feature_to_string(our_features);
1730             their_feature_string = newstralloc(their_feature_string, arg);
1731             their_features = am_string_to_feature(their_feature_string);
1732             if (!their_features) {
1733                 g_warning("Invalid client feature set '%s'", their_feature_string);
1734                 their_features = am_set_default_feature_set();
1735             }
1736             reply(200, "FEATURES %s", our_feature_string);
1737             amfree(our_feature_string);
1738             amfree(their_feature_string);
1739             s[-1] = (char)ch;
1740         } else if (strcmp(cmd, "DATE") == 0 && arg) {
1741             s[-1] = '\0';
1742             target_date = newstralloc(target_date, arg);
1743             reply(200, _("Working date set to %s."), target_date);
1744             s[-1] = (char)ch;
1745         } else if (strcmp(cmd, "DHST") == 0) {
1746             (void)disk_history_list();
1747         } else if (strcmp(cmd, "OISD") == 0 && arg) {
1748             if (is_dir_valid_opaque(arg) != -1) {
1749                 reply(200, _("\"%s\" is a valid directory"), arg);
1750             }
1751         } else if (strcmp(cmd, "OLSD") == 0 && arg) {
1752             (void)opaque_ls(arg,0);
1753         } else if (strcmp(cmd, "ORLD") == 0 && arg) {
1754             (void)opaque_ls(arg, 1);
1755         } else if (strcmp(cmd, "TAPE") == 0) {
1756             (void)tapedev_is();
1757         } else if (strcmp(cmd, "DCMP") == 0) {
1758             (void)are_dumps_compressed();
1759         } else {
1760             *cmd_undo = cmd_undo_ch;    /* restore the command line */
1761             reply(500, _("Command not recognised/incorrect: %s"), cmd);
1762         }
1763         amfree(line);
1764     }
1765     amfree(arg);
1766     
1767     uncompress_remove = remove_files(uncompress_remove);
1768     free_find_result(&output_find);
1769     reply(200, _("Good bye."));
1770     dbclose();
1771     return 0;
1772 }
1773
1774 static char *
1775 amindexd_nicedate(
1776     char *      datestamp)
1777 {
1778     static char nice[20];
1779     int year, month, day;
1780     int hours, minutes, seconds;
1781     char date[9], atime[7];
1782     int  numdate, numtime;
1783
1784     strncpy(date, datestamp, 8);
1785     date[8] = '\0';
1786     numdate = atoi(date);
1787     year  = numdate / 10000;
1788     month = (numdate / 100) % 100;
1789     day   = numdate % 100;
1790
1791     if(strlen(datestamp) <= 8) {
1792         g_snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d",
1793                 year, month, day);
1794     }
1795     else {
1796         strncpy(atime, &(datestamp[8]), 6);
1797         atime[6] = '\0';
1798         numtime = atoi(atime);
1799         hours = numtime / 10000;
1800         minutes = (numtime / 100) % 100;
1801         seconds = numtime % 100;
1802
1803         g_snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d-%02d-%02d-%02d",
1804                 year, month, day, hours, minutes, seconds);
1805     }
1806
1807     return nice;
1808 }
1809
1810 static int
1811 cmp_date(
1812     const char *        date1,
1813     const char *        date2)
1814 {
1815     return strncmp(date1, date2, strlen(date2));
1816 }
1817
1818 static int
1819 get_index_dir(
1820     char *dump_hostname,
1821     char *hostname,
1822     char *diskname)
1823 {
1824     struct stat  dir_stat;
1825     char        *fn;
1826     char        *s;
1827     char        *lower_hostname;
1828
1829     lower_hostname = stralloc(dump_hostname);
1830     for(s=lower_hostname; *s != '\0'; s++)
1831         *s = tolower(*s);
1832
1833     fn = getindexfname(dump_hostname, diskname, NULL, 0);
1834     if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1835         amfree(lower_hostname);
1836         amfree(fn);
1837         return 1;
1838     }
1839     amfree(fn);
1840     if (hostname != NULL) {
1841         fn = getindexfname(hostname, diskname, NULL, 0);
1842         if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1843             amfree(lower_hostname);
1844             amfree(fn);
1845             return 1;
1846         }
1847     }
1848     amfree(fn);
1849     fn = getindexfname(lower_hostname, diskname, NULL, 0);
1850     if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1851         amfree(lower_hostname);
1852         amfree(fn);
1853         return 1;
1854     }
1855     amfree(fn);
1856     if(diskname != NULL) {
1857         fn = getoldindexfname(dump_hostname, diskname, NULL, 0);
1858         if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1859             amfree(lower_hostname);
1860             amfree(fn);
1861             return 1;
1862         }
1863         amfree(fn);
1864         if (hostname != NULL) {
1865             fn = getoldindexfname(hostname, diskname, NULL, 0);
1866             if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1867                 amfree(lower_hostname);
1868                 amfree(fn);
1869                 return 1;
1870             }
1871         }
1872         amfree(fn);
1873         fn = getoldindexfname(lower_hostname, diskname, NULL, 0);
1874         if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1875             amfree(lower_hostname);
1876             amfree(fn);
1877             return 1;
1878         }
1879         amfree(fn);
1880     }
1881     amfree(lower_hostname);
1882     return -1;
1883 }
1884
1885 static char *
1886 get_index_name(
1887     char *dump_hostname,
1888     char *hostname,
1889     char *diskname,
1890     char *timestamps,
1891     int   level)
1892 {
1893     struct stat  dir_stat;
1894     char        *fn;
1895     char        *s;
1896     char        *lower_hostname;
1897
1898     lower_hostname = stralloc(dump_hostname);
1899     for(s=lower_hostname; *s != '\0'; s++)
1900         *s = tolower(*s);
1901
1902     fn = getindexfname(dump_hostname, diskname, timestamps, level);
1903     if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1904         amfree(lower_hostname);
1905         return fn;
1906     }
1907     amfree(fn);
1908     if(hostname != NULL) {
1909         fn = getindexfname(hostname, diskname, timestamps, level);
1910         if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1911             amfree(lower_hostname);
1912             return fn;
1913         }
1914     }
1915     amfree(fn);
1916     fn = getindexfname(lower_hostname, diskname, timestamps, level);
1917     if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1918         amfree(lower_hostname);
1919         return fn;
1920     }
1921     amfree(fn);
1922     if(diskname != NULL) {
1923         fn = getoldindexfname(dump_hostname, diskname, timestamps, level);
1924         if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1925             amfree(lower_hostname);
1926             return fn;
1927         }
1928         amfree(fn);
1929         if(hostname != NULL) {
1930             fn = getoldindexfname(hostname, diskname, timestamps, level);
1931             if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1932                 amfree(lower_hostname);
1933                 return fn;
1934             }
1935         }
1936         amfree(fn);
1937         fn = getoldindexfname(lower_hostname, diskname, timestamps, level);
1938         if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1939             amfree(lower_hostname);
1940             return fn;
1941         }
1942     }
1943     amfree(lower_hostname);
1944     return NULL;
1945 }