bc56ed4f48cbedb83c61ce97b7ab5f95112ab6b7
[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     host_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 server */
683     if (rl->server) {
684         char myhostname[MAX_HOSTNAME_LENGTH+1];
685         if (gethostname(myhostname, MAX_HOSTNAME_LENGTH) == 0) {
686             myhostname[MAX_HOSTNAME_LENGTH] = '\0';
687             g_debug("server hostname: %s", myhostname);
688             if (0 == g_ascii_strcasecmp(peer, myhostname)) {
689                 g_debug("peer matched server ('%s')", myhostname);
690                 return TRUE;
691             }
692         }
693     }
694
695     /* check the match list */
696     for (iter = rl->match_pats; iter; iter = iter->next) {
697         char *pat = iter->data;
698         if (match_host(pat, peer))
699             return TRUE;
700     }
701
702     g_warning("peer '%s' does not match any of the recovery-limit restrictions; rejecting",
703             peer);
704
705     return FALSE;
706 }
707
708 static int
709 is_disk_valid(
710     char *disk)
711 {
712     disk_t *idisk;
713     char *qdisk;
714
715     if (get_config_name() == NULL) {
716         reply(501, _("Must set config,host before setting disk."));
717         return -1;
718     }
719     else if (dump_hostname == NULL) {
720         reply(501, _("Must set host before setting disk."));
721         return -1;
722     }
723
724     /* check that the config actually handles that disk, and that recovery-limit allows it */
725     idisk = lookup_disk(dump_hostname, disk);
726     if(idisk == NULL || !is_disk_allowed(idisk)) {
727         qdisk = quote_string(disk);
728         reply(501, _("Disk %s:%s is not in the server's disklist."), dump_hostname, qdisk);
729         amfree(qdisk);
730         return -1;
731     }
732
733     /* assume an index dir already */
734     if (get_index_dir(dump_hostname, idisk->hostname, disk) == 0) {
735         qdisk = quote_string(disk);
736         reply(501, _("No index records for disk: %s. Invalid?"), qdisk);
737         amfree(qdisk);
738         return -1;
739     }
740
741     return 0;
742 }
743
744
745 static int
746 check_and_load_config(
747     char *      config)
748 {
749     char *conf_diskfile;
750     char *conf_tapelist;
751     char *conf_indexdir;
752     struct stat dir_stat;
753
754     /* check that the config actually exists */
755     if (config == NULL) {
756         reply(501, _("Must set config first."));
757         return -1;
758     }
759
760     /* (re-)initialize configuration with the new config name */
761     config_init(CONFIG_INIT_EXPLICIT_NAME, config);
762     if (config_errors(NULL) >= CFGERR_ERRORS) {
763         reply(501, _("Could not read config file for %s!"), config);
764         return -1;
765     }
766
767     check_running_as(RUNNING_AS_DUMPUSER_PREFERRED);
768
769     conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
770     read_diskfile(conf_diskfile, &disk_list);
771     amfree(conf_diskfile);
772     if (config_errors(NULL) >= CFGERR_ERRORS) {
773         reply(501, _("Could not read disk file %s!"), conf_diskfile);
774         return -1;
775     }
776
777     conf_tapelist = config_dir_relative(getconf_str(CNF_TAPELIST));
778     if(read_tapelist(conf_tapelist)) {
779         reply(501, _("Could not read tapelist file %s!"), conf_tapelist);
780         amfree(conf_tapelist);
781         return -1;
782     }
783     amfree(conf_tapelist);
784
785     dbrename(get_config_name(), DBG_SUBDIR_SERVER);
786
787     output_find = find_dump(&disk_list);
788     /* the 'w' here sorts by write timestamp, so that the first instance of
789      * any particular datestamp/host/disk/level/part that we see is the one
790      * written earlier */
791     sort_find_result("DLKHpwB", &output_find);
792
793     conf_indexdir = config_dir_relative(getconf_str(CNF_INDEXDIR));
794     if (stat (conf_indexdir, &dir_stat) != 0 || !S_ISDIR(dir_stat.st_mode)) {
795         reply(501, _("Index directory %s does not exist"), conf_indexdir);
796         amfree(conf_indexdir);
797         return -1;
798     }
799     amfree(conf_indexdir);
800
801     return 0;
802 }
803
804
805 static int
806 build_disk_table(void)
807 {
808     char *date;
809     char *last_timestamp;
810     off_t last_filenum;
811     int last_level;
812     int last_partnum;
813     find_result_t *find_output;
814
815     if (get_config_name() == NULL) {
816         reply(590, _("Must set config,host,disk before building disk table"));
817         return -1;
818     }
819     else if (dump_hostname == NULL) {
820         reply(590, _("Must set host,disk before building disk table"));
821         return -1;
822     }
823     else if (disk_name == NULL) {
824         reply(590, _("Must set disk before building disk table"));
825         return -1;
826     }
827
828     clear_list();
829     last_timestamp = NULL;
830     last_filenum = (off_t)-1;
831     last_level = -1;
832     last_partnum = -1;
833     for(find_output = output_find;
834         find_output != NULL; 
835         find_output = find_output->next) {
836         if(strcasecmp(dump_hostname, find_output->hostname) == 0 &&
837            strcmp(disk_name    , find_output->diskname)     == 0 &&
838            strcmp("OK"         , find_output->status)       == 0 &&
839            strcmp("OK"         , find_output->dump_status)  == 0) {
840             /*
841              * The sort order puts holding disk entries first.  We want to
842              * use them if at all possible, so ignore any other entries
843              * for the same datestamp after we see a holding disk entry
844              * (as indicated by a filenum of zero).
845              */
846             if(last_timestamp &&
847                strcmp(find_output->timestamp, last_timestamp) == 0 &&
848                find_output->level == last_level && 
849                last_filenum == 0) {
850                 continue;
851             }
852             /* ignore duplicate partnum */
853             if(last_timestamp &&
854                strcmp(find_output->timestamp, last_timestamp) == 0 &&
855                find_output->level == last_level && 
856                find_output->partnum == last_partnum) {
857                 continue;
858             }
859             last_timestamp = find_output->timestamp;
860             last_filenum = find_output->filenum;
861             last_level = find_output->level;
862             last_partnum = find_output->partnum;
863             date = amindexd_nicedate(find_output->timestamp);
864             add_dump(find_output->hostname, date, find_output->level,
865                      find_output->label, find_output->filenum,
866                      find_output->partnum, find_output->totalparts);
867             dbprintf("- %s %d %s %lld %d %d\n",
868                      date, find_output->level, 
869                      find_output->label,
870                      (long long)find_output->filenum,
871                      find_output->partnum, find_output->totalparts);
872         }
873     }
874
875     clean_dump();
876
877     return 0;
878 }
879
880
881 static int
882 disk_history_list(void)
883 {
884     DUMP_ITEM *item;
885     char date[20];
886
887     if (get_config_name() == NULL) {
888         reply(502, _("Must set config,host,disk before listing history"));
889         return -1;
890     }
891     else if (dump_hostname == NULL) {
892         reply(502, _("Must set host,disk before listing history"));
893         return -1;
894     }
895     else if (disk_name == NULL) {
896         reply(502, _("Must set disk before listing history"));
897         return -1;
898     }
899
900     lreply(200, _(" Dump history for config \"%s\" host \"%s\" disk %s"),
901           get_config_name(), dump_hostname, qdisk_name);
902
903     for (item=first_dump(); item!=NULL; item=next_dump(item)){
904         char *tapelist_str = marshal_tapelist(item->tapes, 1);
905
906         strncpy(date, item->date, 20);
907         date[19] = '\0';
908         if(!am_has_feature(their_features,fe_amrecover_timestamp))
909             date[10] = '\0';
910
911         if(am_has_feature(their_features, fe_amindexd_marshall_in_DHST)){
912             lreply(201, " %s %d %s", date, item->level, tapelist_str);
913         }
914         else{
915             lreply(201, " %s %d %s %lld", date, item->level,
916                 tapelist_str, (long long)item->file);
917         }
918         amfree(tapelist_str);
919     }
920
921     reply(200, _("Dump history for config \"%s\" host \"%s\" disk %s"),
922           get_config_name(), dump_hostname, qdisk_name);
923
924     return 0;
925 }
926
927
928 /*
929  * is the directory dir backed up - dir assumed complete relative to
930  * disk mount point
931  */
932 /* opaque version of command */
933 static int
934 is_dir_valid_opaque(
935     char *dir)
936 {
937     DUMP_ITEM *item;
938     char line[STR_SIZE];
939     FILE *fp;
940     int last_level;
941     char *ldir = NULL;
942     char *filename_gz = NULL;
943     char *filename = NULL;
944     size_t ldir_len;
945     GPtrArray *emsg = NULL;
946
947     if (get_config_name() == NULL || dump_hostname == NULL || disk_name == NULL) {
948         reply(502, _("Must set config,host,disk before asking about directories"));
949         return -1;
950     }
951     else if (dump_hostname == NULL) {
952         reply(502, _("Must set host,disk before asking about directories"));
953         return -1;
954     }
955     else if (disk_name == NULL) {
956         reply(502, _("Must set disk before asking about directories"));
957         return -1;
958     }
959     else if (target_date == NULL) {
960         reply(502, _("Must set date before asking about directories"));
961         return -1;
962     }
963     /* scan through till we find first dump on or before date */
964     for (item=first_dump(); item!=NULL; item=next_dump(item))
965         if (cmp_date(item->date, target_date) <= 0)
966             break;
967
968     if (item == NULL)
969     {
970         /* no dump for given date */
971         reply(500, _("No dumps available on or before date \"%s\""), target_date);
972         return -1;
973     }
974
975     if(strcmp(dir, "/") == 0) {
976         ldir = stralloc(dir);
977     } else {
978         ldir = stralloc2(dir, "/");
979     }
980     ldir_len = strlen(ldir);
981
982     /* go back till we hit a level 0 dump */
983     do
984     {
985         amfree(filename);
986         filename_gz = get_index_name(dump_hostname, item->hostname, disk_name,
987                                      item->date, item->level);
988         if (filename_gz == NULL) {
989             reply(599, "index not found");
990             amfree(ldir);
991             return -1;
992         }
993         emsg = g_ptr_array_new();
994         if((filename = uncompress_file(filename_gz, &emsg)) == NULL) {
995             reply_ptr_array(599, emsg);
996             amfree(filename_gz);
997             g_ptr_array_free_full(emsg);
998             amfree(ldir);
999             return -1;
1000         }
1001         g_ptr_array_free_full(emsg);
1002         amfree(filename_gz);
1003         dbprintf("f %s\n", filename);
1004         if ((fp = fopen(filename, "r")) == NULL) {
1005             reply(599, _("System error: %s"), strerror(errno));
1006             amfree(filename);
1007             amfree(ldir);
1008             return -1;
1009         }
1010         while (fgets(line, STR_SIZE, fp) != NULL) {
1011             if (line[0] == '\0')
1012                 continue;
1013             if(line[strlen(line)-1] == '\n')
1014                 line[strlen(line)-1] = '\0';
1015             if (strncmp(line, ldir, ldir_len) != 0) {
1016                 continue;                       /* not found yet */
1017             }
1018             amfree(filename);
1019             amfree(ldir);
1020             afclose(fp);
1021             return 0;
1022         }
1023         afclose(fp);
1024
1025         last_level = item->level;
1026         do
1027         {
1028             item=next_dump(item);
1029         } while ((item != NULL) && (item->level >= last_level));
1030     } while (item != NULL);
1031
1032     amfree(filename);
1033     amfree(ldir);
1034     reply(500, _("\"%s\" is an invalid directory"), dir);
1035     return -1;
1036 }
1037
1038 static int
1039 opaque_ls(
1040     char *      dir,
1041     int         recursive)
1042 {
1043     DUMP_ITEM *dump_item;
1044     DIR_ITEM *dir_item;
1045     int level, last_level;
1046     GPtrArray *emsg = NULL;
1047     am_feature_e marshall_feature;
1048
1049     if (recursive) {
1050         marshall_feature = fe_amindexd_marshall_in_ORLD;
1051     } else {
1052         marshall_feature = fe_amindexd_marshall_in_OLSD;
1053     }
1054
1055     clear_dir_list();
1056
1057     if (get_config_name() == NULL) {
1058         reply(502, _("Must set config,host,disk before listing a directory"));
1059         return -1;
1060     }
1061     else if (dump_hostname == NULL) {
1062         reply(502, _("Must set host,disk before listing a directory"));
1063         return -1;
1064     }
1065     else if (disk_name == NULL) {
1066         reply(502, _("Must set disk before listing a directory"));
1067         return -1;
1068     }
1069     else if (target_date == NULL) {
1070         reply(502, _("Must set date before listing a directory"));
1071         return -1;
1072     }
1073
1074     /* scan through till we find first dump on or before date */
1075     for (dump_item=first_dump(); dump_item!=NULL; dump_item=next_dump(dump_item))
1076         if (cmp_date(dump_item->date, target_date) <= 0)
1077             break;
1078
1079     if (dump_item == NULL)
1080     {
1081         /* no dump for given date */
1082         reply(500, _("No dumps available on or before date \"%s\""), target_date);
1083         return -1;
1084     }
1085
1086     /* get data from that dump */
1087     emsg = g_ptr_array_new();
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     /* go back processing higher level dumps till we hit a level 0 dump */
1095     last_level = dump_item->level;
1096     while ((last_level != 0) && ((dump_item=next_dump(dump_item)) != NULL))
1097     {
1098         if (dump_item->level < last_level)
1099         {
1100             last_level = dump_item->level;
1101             if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
1102                 reply_ptr_array(599, emsg);
1103                 g_ptr_array_free_full(emsg);
1104                 return -1;
1105             }
1106         }
1107     }
1108     g_ptr_array_free_full(emsg);
1109
1110     /* return the information to the caller */
1111     lreply(200, _(" Opaque list of %s"), dir);
1112     for(level=0; level < DUMP_LEVELS; level++) {
1113         for (dir_item = get_dir_list(); dir_item != NULL; 
1114              dir_item = dir_item->next) {
1115
1116             if(dir_item->dump->level == level) {
1117                 if (!am_has_feature(their_features, marshall_feature) &&
1118                     (num_entries(dir_item->dump->tapes) > 1 ||
1119                     dir_item->dump->tapes->numfiles > 1)) {
1120                     fast_lreply(501, _(" ERROR: Split dumps not supported"
1121                                 " with old version of amrecover."));
1122                     break;
1123                 }
1124                 else {
1125                     opaque_ls_one(dir_item, marshall_feature, recursive);
1126                 }
1127             }
1128         }
1129     }
1130     reply(200, _(" Opaque list of %s"), dir);
1131
1132     clear_dir_list();
1133     return 0;
1134 }
1135
1136 void opaque_ls_one(
1137     DIR_ITEM *   dir_item,
1138     am_feature_e marshall_feature,
1139     int          recursive)
1140 {
1141     char date[20];
1142     char *tapelist_str;
1143     char *qtapelist_str;
1144     char *qpath;
1145
1146     if (am_has_feature(their_features, marshall_feature)) {
1147         tapelist_str = marshal_tapelist(dir_item->dump->tapes, 1);
1148     } else {
1149         tapelist_str = dir_item->dump->tapes->label;
1150     }
1151
1152     if (am_has_feature(their_features, fe_amindexd_quote_label)) {
1153         qtapelist_str = quote_string(tapelist_str);
1154     } else {
1155         qtapelist_str = stralloc(tapelist_str);
1156     }
1157     strncpy(date, dir_item->dump->date, 20);
1158     date[19] = '\0';
1159     if(!am_has_feature(their_features,fe_amrecover_timestamp))
1160         date[10] = '\0';
1161
1162     qpath = quote_string(dir_item->path);
1163     if((!recursive && am_has_feature(their_features,
1164                                      fe_amindexd_fileno_in_OLSD)) ||
1165        (recursive && am_has_feature(their_features,
1166                                     fe_amindexd_fileno_in_ORLD))) {
1167         fast_lreply(201, " %s %d %s %lld %s",
1168                     date,
1169                     dir_item->dump->level,
1170                     qtapelist_str,
1171                     (long long)dir_item->dump->file,
1172                     qpath);
1173     }
1174     else {
1175
1176         fast_lreply(201, " %s %d %s %s",
1177                     date, dir_item->dump->level,
1178                     qtapelist_str, qpath);
1179     }
1180     amfree(qpath);
1181     if(am_has_feature(their_features, marshall_feature)) {
1182         amfree(tapelist_str);
1183     }
1184     amfree(qtapelist_str);
1185 }
1186
1187 /*
1188  * returns the value of changer or tapedev from the amanda.conf file if set,
1189  * otherwise reports an error
1190  */
1191
1192 static int
1193 tapedev_is(void)
1194 {
1195     char *result;
1196
1197     /* check state okay to do this */
1198     if (get_config_name() == NULL) {
1199         reply(501, _("Must set config before asking about tapedev."));
1200         return -1;
1201     }
1202
1203     /* use amrecover_changer if possible */
1204     if ((result = getconf_str(CNF_AMRECOVER_CHANGER)) != NULL  &&
1205         *result != '\0') {
1206         dbprintf(_("tapedev_is amrecover_changer: %s\n"), result);
1207         reply(200, "%s", result);
1208         return 0;
1209     }
1210
1211     /* use changer if possible */
1212     if ((result = getconf_str(CNF_TPCHANGER)) != NULL  &&  *result != '\0') {
1213         dbprintf(_("tapedev_is tpchanger: %s\n"), result);
1214         reply(200, "%s", result);
1215         return 0;
1216     }
1217
1218     /* get tapedev value */
1219     if ((result = getconf_str(CNF_TAPEDEV)) != NULL  &&  *result != '\0') {
1220         dbprintf(_("tapedev_is tapedev: %s\n"), result);
1221         reply(200, "%s", result);
1222         return 0;
1223     }
1224
1225     dbprintf(_("No tapedev or tpchanger in config site.\n"));
1226
1227     reply(501, _("Tapedev or tpchanger not set in config file."));
1228     return -1;
1229 }
1230
1231
1232 /* returns YES if dumps for disk are compressed, NO if not */
1233 static int
1234 are_dumps_compressed(void)
1235 {
1236     disk_t *diskp;
1237
1238     /* check state okay to do this */
1239     if (get_config_name() == NULL) {
1240         reply(501, _("Must set config,host,disk name before asking about dumps."));
1241         return -1;
1242     }
1243     else if (dump_hostname == NULL) {
1244         reply(501, _("Must set host,disk name before asking about dumps."));
1245         return -1;
1246     }
1247     else if (disk_name == NULL) {
1248         reply(501, _("Must set disk name before asking about dumps."));
1249         return -1;
1250     }
1251
1252     /* now go through the list of disks and find which have indexes */
1253     for (diskp = disk_list.head; diskp != NULL; diskp = diskp->next) {
1254         if ((strcasecmp(diskp->host->hostname, dump_hostname) == 0)
1255                 && (strcmp(diskp->name, disk_name) == 0)) {
1256             break;
1257         }
1258     }
1259
1260     if (diskp == NULL) {
1261         reply(501, _("Couldn't find host/disk in disk file."));
1262         return -1;
1263     }
1264
1265     /* send data to caller */
1266     if (diskp->compress == COMP_NONE)
1267         reply(200, "NO");
1268     else
1269         reply(200, "YES");
1270
1271     return 0;
1272 }
1273
1274 int
1275 main(
1276     int         argc,
1277     char **     argv)
1278 {
1279     char *line = NULL, *part = NULL;
1280     char *s;
1281     int ch;
1282     char *cmd_undo, cmd_undo_ch;
1283     socklen_t_equiv socklen;
1284     sockaddr_union his_addr;
1285     char *arg = NULL;
1286     char *cmd;
1287     size_t len;
1288     int user_validated = 0;
1289     char *errstr = NULL;
1290     char *pgm = "amindexd";             /* in case argv[0] is not set */
1291     char his_hostname[MAX_HOSTNAME_LENGTH];
1292     char *cfg_opt = NULL;
1293
1294     if (argc > 1 && argv && argv[1] && g_str_equal(argv[1], "--version")) {
1295         printf("amindexd-%s\n", VERSION);
1296         return (0);
1297     }
1298
1299     /*
1300      * Configure program for internationalization:
1301      *   1) Only set the message locale for now.
1302      *   2) Set textdomain for all amanda related programs to "amanda"
1303      *      We don't want to be forced to support dozens of message catalogs.
1304      */  
1305     setlocale(LC_MESSAGES, "C");
1306     textdomain("amanda"); 
1307
1308     safe_fd(DATA_FD_OFFSET, 2);
1309     openbsd_fd_inform();
1310     safe_cd();
1311
1312     /*
1313      * When called via inetd, it is not uncommon to forget to put the
1314      * argv[0] value on the config line.  On some systems (e.g. Solaris)
1315      * this causes argv and/or argv[0] to be NULL, so we have to be
1316      * careful getting our name.
1317      */
1318     if (argc >= 1 && argv != NULL && argv[0] != NULL) {
1319         if((pgm = strrchr(argv[0], '/')) != NULL) {
1320             pgm++;
1321         } else {
1322             pgm = argv[0];
1323         }
1324     }
1325
1326     set_pname(pgm);
1327
1328     /* Don't die when child closes pipe */
1329     signal(SIGPIPE, SIG_IGN);
1330
1331     dbopen(DBG_SUBDIR_SERVER);
1332     dbprintf(_("version %s\n"), VERSION);
1333
1334     if(argv == NULL) {
1335         error("argv == NULL\n");
1336     }
1337
1338     if (! (argc >= 1 && argv[0] != NULL)) {
1339         dbprintf(_("WARNING: argv[0] not defined: check inetd.conf\n"));
1340     }
1341
1342     debug_dup_stderr_to_debug();
1343
1344     /* initialize */
1345
1346     argc--;
1347     argv++;
1348
1349     if(argc > 0 && strcmp(*argv, "-t") == 0) {
1350         amindexd_debug = 1;
1351         argc--;
1352         argv++;
1353     }
1354
1355     if(argc > 0 && strcmp(*argv, "amandad") == 0) {
1356         from_amandad = 1;
1357         argc--;
1358         argv++;
1359         if(argc > 0) {
1360             amandad_auth = *argv;
1361             argc--;
1362             argv++;
1363         }
1364     }
1365     else {
1366         from_amandad = 0;
1367         safe_fd(dbfd(), 1);
1368     }
1369
1370     if (argc > 0) {
1371         cfg_opt = *argv;
1372         argc--;
1373         argv++;
1374     }
1375
1376     if(gethostname(local_hostname, SIZEOF(local_hostname)-1) == -1) {
1377         error(_("gethostname: %s"), strerror(errno));
1378         /*NOTREACHED*/
1379     }
1380     local_hostname[SIZEOF(local_hostname)-1] = '\0';
1381
1382     /* now trim domain off name */
1383     s = local_hostname;
1384     ch = *s++;
1385     while(ch && ch != '.') ch = *s++;
1386     s[-1] = '\0';
1387
1388
1389     if(from_amandad == 0) {
1390         if(!amindexd_debug) {
1391             /* who are we talking to? */
1392             socklen = sizeof (his_addr);
1393             if (getpeername(0, (struct sockaddr *)&his_addr, &socklen) == -1)
1394                 error(_("getpeername: %s"), strerror(errno));
1395
1396             /* Try a reverse (IP->hostname) resolution, and fail if it does
1397              * not work -- this is a basic security check */
1398             if (getnameinfo((struct sockaddr *)&his_addr, SS_LEN(&his_addr),
1399                             his_hostname, sizeof(his_hostname),
1400                             NULL, 0,
1401                             0)) {
1402                 error(_("getnameinfo(%s): hostname lookup failed"),
1403                       str_sockaddr(&his_addr));
1404                 /*NOTREACHED*/
1405             }
1406         }
1407
1408         /* Set up the input and output FILEs */
1409         cmdout = stdout;
1410         cmdin = stdin;
1411     }
1412     else {
1413         /* read the REQ packet */
1414         for(; (line = agets(stdin)) != NULL; free(line)) {
1415             if(strncmp_const(line, "OPTIONS ") == 0) {
1416                 if (g_options != NULL) {
1417                     dbprintf(_("REQ packet specified multiple OPTIONS.\n"));
1418                     free_g_options(g_options);
1419                 }
1420                 g_options = parse_g_options(line+8, 1);
1421                 if(!g_options->hostname) {
1422                     g_options->hostname = alloc(MAX_HOSTNAME_LENGTH+1);
1423                     gethostname(g_options->hostname, MAX_HOSTNAME_LENGTH);
1424                     g_options->hostname[MAX_HOSTNAME_LENGTH] = '\0';
1425                 }
1426             }
1427         }
1428         amfree(line);
1429
1430         if(amandad_auth && g_options->auth) {
1431             if(strcasecmp(amandad_auth, g_options->auth) != 0) {
1432                 g_printf(_("ERROR recover program ask for auth=%s while amindexd is configured for '%s'\n"),
1433                        g_options->auth, amandad_auth);
1434                 error(_("amindexd: ERROR recover program ask for auth=%s while amindexd is configured for '%s'"),
1435                       g_options->auth, amandad_auth);
1436                 /*NOTREACHED*/
1437             }
1438         }
1439         /* send the REP packet */
1440         g_printf("CONNECT MESG %d\n", DATA_FD_OFFSET);
1441         g_printf("\n");
1442         fflush(stdin);
1443         fflush(stdout);
1444         fclose(stdin);
1445         fclose(stdout);
1446         
1447         cmdout = fdopen(DATA_FD_OFFSET + 0, "a");
1448         if (!cmdout) {
1449             error(_("amindexd: Can't fdopen(%d): %s"), DATA_FD_OFFSET + 0, strerror(errno));
1450             /*NOTREACHED*/
1451         }
1452
1453         cmdin = fdopen(DATA_FD_OFFSET + 1, "r");
1454         if (!cmdin) {
1455             error(_("amindexd: Can't fdopen(%d): %s"), DATA_FD_OFFSET + 1, strerror(errno));
1456             /*NOTREACHED*/
1457         }
1458     }
1459
1460     /* clear these so we can detect when the have not been set by the client */
1461     amfree(dump_hostname);
1462     amfree(qdisk_name);
1463     amfree(disk_name);
1464     amfree(target_date);
1465
1466     our_features = am_init_feature_set();
1467     their_features = am_set_default_feature_set();
1468
1469     if (cfg_opt != NULL && check_and_load_config(cfg_opt) != -1) { /* load the config */
1470         return 1;
1471     }
1472
1473     reply(220, _("%s AMANDA index server (%s) ready."), local_hostname,
1474           VERSION);
1475
1476     user_validated = from_amandad;
1477
1478     /* a real simple parser since there are only a few commands */
1479     while (1)
1480     {
1481         /* get a line from the client */
1482         while(1) {
1483             if((part = agets(cmdin)) == NULL) {
1484                 if(errno != 0) {
1485                     dbprintf(_("? read error: %s\n"), strerror(errno));
1486                 } else {
1487                     dbprintf(_("? unexpected EOF\n"));
1488                 }
1489                 if(line) {
1490                     dbprintf(_("? unprocessed input:\n"));
1491                     dbprintf("-----\n");
1492                     dbprintf("? %s\n", line);
1493                     dbprintf("-----\n");
1494                 }
1495                 amfree(line);
1496                 amfree(part);
1497                 uncompress_remove = remove_files(uncompress_remove);
1498                 dbclose();
1499                 return 1;               /* they hung up? */
1500             }
1501             strappend(line, part);      /* Macro: line can be null */
1502             amfree(part);
1503
1504             if(amindexd_debug) {
1505                 break;                  /* we have a whole line */
1506             }
1507             if((len = strlen(line)) > 0 && line[len-1] == '\r') {
1508                 line[len-1] = '\0';     /* zap the '\r' */
1509                 break;
1510             }
1511             /*
1512              * Hmmm.  We got a "line" from agets(), which means it saw
1513              * a '\n' (or EOF, etc), but there was not a '\r' before it.
1514              * Put a '\n' back in the buffer and loop for more.
1515              */
1516             strappend(line, "\n");
1517         }
1518
1519         dbprintf("> %s\n", line);
1520
1521         if (arg != NULL)
1522             amfree(arg);
1523         s = line;
1524         ch = *s++;
1525
1526         skip_whitespace(s, ch);
1527         if(ch == '\0') {
1528             reply(500, _("Command not recognised/incorrect: %s"), line);
1529             amfree(line);
1530             continue;
1531         }
1532         cmd = s - 1;
1533
1534         skip_non_whitespace(s, ch);
1535         cmd_undo = s-1;                         /* for error message */
1536         cmd_undo_ch = *cmd_undo;
1537         *cmd_undo = '\0';
1538         if (ch) {
1539             skip_whitespace(s, ch);             /* find the argument */
1540             if (ch) {
1541                 arg = s-1;
1542                 skip_quoted_string(s, ch);
1543                 arg = unquote_string(arg);
1544             }
1545         }
1546
1547         amfree(errstr);
1548         if (!user_validated && strcmp(cmd, "SECURITY") == 0 && arg) {
1549             user_validated = amindexd_debug ||
1550                                 check_security(
1551                                         (sockaddr_union *)&his_addr,
1552                                         arg, 0, &errstr);
1553             if(user_validated) {
1554                 reply(200, _("Access OK"));
1555                 amfree(line);
1556                 continue;
1557             }
1558         }
1559         if (!user_validated) {  /* don't tell client the reason, just log it to debug log */
1560             reply(500, _("Access not allowed"));
1561             if (errstr) {   
1562                 dbprintf("%s\n", errstr);
1563             }
1564             break;
1565         }
1566
1567         if (strcmp(cmd, "QUIT") == 0) {
1568             amfree(line);
1569             break;
1570         } else if (strcmp(cmd, "HOST") == 0 && arg) {
1571             am_host_t *lhost;
1572             /* set host we are restoring */
1573             s[-1] = '\0';
1574             if ((lhost = is_dump_host_valid(arg)) != NULL)
1575             {
1576                 dump_hostname = newstralloc(dump_hostname, lhost->hostname);
1577                 reply(200, _("Dump host set to %s."), dump_hostname);
1578                 amfree(qdisk_name);             /* invalidate any value */
1579                 amfree(disk_name);              /* invalidate any value */
1580             }
1581             s[-1] = (char)ch;
1582         } else if (strcmp(cmd, "LISTHOST") == 0) {
1583             disk_t *disk, 
1584                    *diskdup;
1585             int nbhost = 0,
1586                 found = 0;
1587             s[-1] = '\0';
1588             if (get_config_name() == NULL) {
1589                 reply(501, _("Must set config before listhost"));
1590             }
1591             else {
1592                 lreply(200, _(" List hosts for config %s"), get_config_name());
1593                 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1594                     found = 0;
1595                     for (diskdup = disk_list.head; diskdup!=disk; diskdup = diskdup->next) {
1596                         if(strcmp(diskdup->host->hostname, disk->host->hostname) == 0) {
1597                           found = 1;
1598                           break;
1599                         }
1600                     }
1601                     if(!found){
1602                         fast_lreply(201, " %s", disk->host->hostname);
1603                         nbhost++;
1604                     }
1605                 }
1606                 if(nbhost > 0) {
1607                     reply(200, _(" List hosts for config %s"), get_config_name());
1608                 }
1609                 else {
1610                     reply(200, _("No hosts for config %s"), get_config_name());
1611                 }
1612             }
1613             s[-1] = (char)ch;
1614         } else if (strcmp(cmd, "DISK") == 0 && arg) {
1615             s[-1] = '\0';
1616             if (is_disk_valid(arg) != -1) {
1617                 disk_name = newstralloc(disk_name, arg);
1618                 qdisk_name = quote_string(disk_name);
1619                 if (build_disk_table() != -1) {
1620                     reply(200, _("Disk set to %s."), qdisk_name);
1621                 }
1622             }
1623             s[-1] = (char)ch;
1624         } else if (strcmp(cmd, "DLE") == 0) {
1625             disk_t *dp;
1626             char *optionstr;
1627             char *b64disk;
1628             char *l, *ql;
1629
1630             dp = lookup_disk(dump_hostname, disk_name);
1631             if (dp->line == 0) {
1632                 reply(200, "NODLE");
1633             } else {
1634                 GPtrArray *errarray;
1635                 guint      i;
1636
1637                 b64disk = amxml_format_tag("disk", dp->name);
1638                 dp->host->features = their_features;
1639                 errarray = validate_optionstr(dp);
1640                 if (errarray->len > 0) {
1641                     for (i=0; i < errarray->len; i++) {
1642                         g_debug(_("ERROR: %s:%s %s"),
1643                                 dump_hostname, disk_name,
1644                                 (char *)g_ptr_array_index(errarray, i));
1645                     }
1646                     g_ptr_array_free(errarray, TRUE);
1647                     reply(200, "NODLE");
1648                 } else {
1649                     optionstr = xml_optionstr(dp, 0);
1650                     l = vstralloc("<dle>\n",
1651                               "  <program>", dp->program, "</program>\n", NULL);
1652                     if (dp->application) {
1653                         application_t *application;
1654                         char *xml_app;
1655
1656                         application = lookup_application(dp->application);
1657                         g_assert(application != NULL);
1658                         xml_app = xml_application(dp, application,
1659                                                   their_features);
1660                         vstrextend(&l, xml_app, NULL);
1661                         amfree(xml_app);
1662                     }
1663                     vstrextend(&l, "  ", b64disk, "\n", NULL);
1664                     if (dp->device) {
1665                         char *b64device = amxml_format_tag("diskdevice",
1666                                                            dp->device);
1667                         vstrextend(&l, "  ", b64device, "\n", NULL);
1668                         amfree(b64device);
1669                     }
1670                     vstrextend(&l, optionstr, "</dle>\n", NULL);
1671                     ql = quote_string(l);
1672                     reply(200, "%s", ql);
1673                     amfree(optionstr);
1674                     amfree(l);
1675                     amfree(ql);
1676                     amfree(b64disk);
1677                 }
1678             }
1679         } else if (strcmp(cmd, "LISTDISK") == 0) {
1680             char *qname;
1681             disk_t *disk;
1682             int nbdisk = 0;
1683             s[-1] = '\0';
1684             if (get_config_name() == NULL) {
1685                 reply(501, _("Must set config, host before listdisk"));
1686             }
1687             else if (dump_hostname == NULL) {
1688                 reply(501, _("Must set host before listdisk"));
1689             }
1690             else if(arg) {
1691                 lreply(200, _(" List of disk for device %s on host %s"), arg,
1692                        dump_hostname);
1693                 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1694
1695                     if (strcasecmp(disk->host->hostname, dump_hostname) == 0 &&
1696                       ((disk->device && strcmp(disk->device, arg) == 0) ||
1697                       (!disk->device && strcmp(disk->name, arg) == 0))) {
1698                         qname = quote_string(disk->name);
1699                         fast_lreply(201, " %s", qname);
1700                         amfree(qname);
1701                         nbdisk++;
1702                     }
1703                 }
1704                 if(nbdisk > 0) {
1705                     reply(200, _("List of disk for device %s on host %s"), arg,
1706                           dump_hostname);
1707                 }
1708                 else {
1709                     reply(200, _("No disk for device %s on host %s"), arg,
1710                           dump_hostname);
1711                 }
1712             }
1713             else {
1714                 lreply(200, _(" List of disk for host %s"), dump_hostname);
1715                 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1716                     if(strcasecmp(disk->host->hostname, dump_hostname) == 0) {
1717                         qname = quote_string(disk->name);
1718                         fast_lreply(201, " %s", qname);
1719                         amfree(qname);
1720                         nbdisk++;
1721                     }
1722                 }
1723                 if(nbdisk > 0) {
1724                     reply(200, _("List of disk for host %s"), dump_hostname);
1725                 }
1726                 else {
1727                     reply(200, _("No disk for host %s"), dump_hostname);
1728                 }
1729             }
1730             s[-1] = (char)ch;
1731         } else if (strcmp(cmd, "SCNF") == 0 && arg) {
1732             s[-1] = '\0';
1733             if (check_and_load_config(arg) != -1) {    /* try to load the new config */
1734                 amfree(dump_hostname);          /* invalidate any value */
1735                 amfree(qdisk_name);             /* invalidate any value */
1736                 amfree(disk_name);              /* invalidate any value */
1737                 reply(200, _("Config set to %s."), get_config_name());
1738             } /* check_and_load_config replies with any failure messages */
1739             s[-1] = (char)ch;
1740         } else if (strcmp(cmd, "FEATURES") == 0 && arg) {
1741             char *our_feature_string = NULL;
1742             char *their_feature_string = NULL;
1743             s[-1] = '\0';
1744             am_release_feature_set(our_features);
1745             am_release_feature_set(their_features);
1746             our_features = am_init_feature_set();
1747             our_feature_string = am_feature_to_string(our_features);
1748             their_feature_string = newstralloc(their_feature_string, arg);
1749             their_features = am_string_to_feature(their_feature_string);
1750             if (!their_features) {
1751                 g_warning("Invalid client feature set '%s'", their_feature_string);
1752                 their_features = am_set_default_feature_set();
1753             }
1754             reply(200, "FEATURES %s", our_feature_string);
1755             amfree(our_feature_string);
1756             amfree(their_feature_string);
1757             s[-1] = (char)ch;
1758         } else if (strcmp(cmd, "DATE") == 0 && arg) {
1759             s[-1] = '\0';
1760             target_date = newstralloc(target_date, arg);
1761             reply(200, _("Working date set to %s."), target_date);
1762             s[-1] = (char)ch;
1763         } else if (strcmp(cmd, "DHST") == 0) {
1764             (void)disk_history_list();
1765         } else if (strcmp(cmd, "OISD") == 0 && arg) {
1766             if (is_dir_valid_opaque(arg) != -1) {
1767                 reply(200, _("\"%s\" is a valid directory"), arg);
1768             }
1769         } else if (strcmp(cmd, "OLSD") == 0 && arg) {
1770             (void)opaque_ls(arg,0);
1771         } else if (strcmp(cmd, "ORLD") == 0 && arg) {
1772             (void)opaque_ls(arg, 1);
1773         } else if (strcmp(cmd, "TAPE") == 0) {
1774             (void)tapedev_is();
1775         } else if (strcmp(cmd, "DCMP") == 0) {
1776             (void)are_dumps_compressed();
1777         } else {
1778             *cmd_undo = cmd_undo_ch;    /* restore the command line */
1779             reply(500, _("Command not recognised/incorrect: %s"), cmd);
1780         }
1781         amfree(line);
1782     }
1783     amfree(arg);
1784     
1785     uncompress_remove = remove_files(uncompress_remove);
1786     free_find_result(&output_find);
1787     reply(200, _("Good bye."));
1788     dbclose();
1789     return 0;
1790 }
1791
1792 static char *
1793 amindexd_nicedate(
1794     char *      datestamp)
1795 {
1796     static char nice[20];
1797     int year, month, day;
1798     int hours, minutes, seconds;
1799     char date[9], atime[7];
1800     int  numdate, numtime;
1801
1802     strncpy(date, datestamp, 8);
1803     date[8] = '\0';
1804     numdate = atoi(date);
1805     year  = numdate / 10000;
1806     month = (numdate / 100) % 100;
1807     day   = numdate % 100;
1808
1809     if(strlen(datestamp) <= 8) {
1810         g_snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d",
1811                 year, month, day);
1812     }
1813     else {
1814         strncpy(atime, &(datestamp[8]), 6);
1815         atime[6] = '\0';
1816         numtime = atoi(atime);
1817         hours = numtime / 10000;
1818         minutes = (numtime / 100) % 100;
1819         seconds = numtime % 100;
1820
1821         g_snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d-%02d-%02d-%02d",
1822                 year, month, day, hours, minutes, seconds);
1823     }
1824
1825     return nice;
1826 }
1827
1828 static int
1829 cmp_date(
1830     const char *        date1,
1831     const char *        date2)
1832 {
1833     return strncmp(date1, date2, strlen(date2));
1834 }
1835
1836 static int
1837 get_index_dir(
1838     char *dump_hostname,
1839     char *hostname,
1840     char *diskname)
1841 {
1842     struct stat  dir_stat;
1843     char        *fn;
1844     char        *s;
1845     char        *lower_hostname;
1846
1847     lower_hostname = stralloc(dump_hostname);
1848     for(s=lower_hostname; *s != '\0'; s++)
1849         *s = tolower(*s);
1850
1851     fn = getindexfname(dump_hostname, diskname, NULL, 0);
1852     if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1853         amfree(lower_hostname);
1854         amfree(fn);
1855         return 1;
1856     }
1857     amfree(fn);
1858     if (hostname != NULL) {
1859         fn = getindexfname(hostname, diskname, NULL, 0);
1860         if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1861             amfree(lower_hostname);
1862             amfree(fn);
1863             return 1;
1864         }
1865     }
1866     amfree(fn);
1867     fn = getindexfname(lower_hostname, diskname, NULL, 0);
1868     if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1869         amfree(lower_hostname);
1870         amfree(fn);
1871         return 1;
1872     }
1873     amfree(fn);
1874     if(diskname != NULL) {
1875         fn = getoldindexfname(dump_hostname, diskname, NULL, 0);
1876         if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1877             amfree(lower_hostname);
1878             amfree(fn);
1879             return 1;
1880         }
1881         amfree(fn);
1882         if (hostname != NULL) {
1883             fn = getoldindexfname(hostname, diskname, NULL, 0);
1884             if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1885                 amfree(lower_hostname);
1886                 amfree(fn);
1887                 return 1;
1888             }
1889         }
1890         amfree(fn);
1891         fn = getoldindexfname(lower_hostname, diskname, NULL, 0);
1892         if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1893             amfree(lower_hostname);
1894             amfree(fn);
1895             return 1;
1896         }
1897         amfree(fn);
1898     }
1899     amfree(lower_hostname);
1900     return -1;
1901 }
1902
1903 static char *
1904 get_index_name(
1905     char *dump_hostname,
1906     char *hostname,
1907     char *diskname,
1908     char *timestamps,
1909     int   level)
1910 {
1911     struct stat  dir_stat;
1912     char        *fn;
1913     char        *s;
1914     char        *lower_hostname;
1915
1916     lower_hostname = stralloc(dump_hostname);
1917     for(s=lower_hostname; *s != '\0'; s++)
1918         *s = tolower(*s);
1919
1920     fn = getindexfname(dump_hostname, diskname, timestamps, level);
1921     if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1922         amfree(lower_hostname);
1923         return fn;
1924     }
1925     amfree(fn);
1926     if(hostname != NULL) {
1927         fn = getindexfname(hostname, diskname, timestamps, level);
1928         if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1929             amfree(lower_hostname);
1930             return fn;
1931         }
1932     }
1933     amfree(fn);
1934     fn = getindexfname(lower_hostname, diskname, timestamps, level);
1935     if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1936         amfree(lower_hostname);
1937         return fn;
1938     }
1939     amfree(fn);
1940     if(diskname != NULL) {
1941         fn = getoldindexfname(dump_hostname, diskname, timestamps, level);
1942         if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1943             amfree(lower_hostname);
1944             return fn;
1945         }
1946         amfree(fn);
1947         if(hostname != NULL) {
1948             fn = getoldindexfname(hostname, diskname, timestamps, level);
1949             if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1950                 amfree(lower_hostname);
1951                 return fn;
1952             }
1953         }
1954         amfree(fn);
1955         fn = getoldindexfname(lower_hostname, diskname, timestamps, level);
1956         if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1957             amfree(lower_hostname);
1958             return fn;
1959         }
1960     }
1961     amfree(lower_hostname);
1962     return NULL;
1963 }