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