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