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