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