Imported Upstream version 2.5.1
[debian/amanda] / server-src / amindexd.c
index f721e2cf97bf18115be7d19b1debe43ce8da1cdb..96e2483a575360b7c0c5e32e8aabfdfde10e0311 100644 (file)
@@ -24,7 +24,7 @@
  * file named AUTHORS, in the root directory of this distribution.
  */
 /*
- * $Id: amindexd.c,v 1.86 2006/03/09 16:51:41 martinea Exp $
+ * $Id: amindexd.c,v 1.106 2006/07/25 18:27:57 martinea Exp $
  *
  * This is the server daemon part of the index client/server system.
  * It is assumed that this is launched from inetd instead of being
 #include "token.h"
 #include "find.h"
 #include "tapefile.h"
-
-#ifdef HAVE_NETINET_IN_SYSTM_H
-#include <netinet/in_systm.h>
-#endif
-
-#ifdef HAVE_NETINET_IP_H
-#include <netinet/ip.h>
-#endif
+#include "util.h"
+#include "amandad.h"
 
 #include <grp.h>
 
@@ -71,13 +65,17 @@ typedef struct REMOVE_ITEM
 } REMOVE_ITEM;
 
 /* state */
-char local_hostname[MAX_HOSTNAME_LENGTH+1];    /* me! */
-char *remote_hostname = NULL;                  /* the client */
-char *dump_hostname = NULL;                    /* machine we are restoring */
-char *disk_name;                               /* disk we are restoring */
-char *target_date = NULL;
-disklist_t disk_list;                          /* all disks in cur config */
-find_result_t *output_find = NULL;
+static int from_amandad;
+static char local_hostname[MAX_HOSTNAME_LENGTH+1];     /* me! */
+static char *remote_hostname = NULL;                   /* the client */
+static char *dump_hostname = NULL;             /* machine we are restoring */
+static char *disk_name;                                /* disk we are restoring */
+char *qdisk_name = NULL;                       /* disk we are restoring */
+static char *target_date = NULL;
+static disklist_t disk_list;                   /* all disks in cur config */
+static find_result_t *output_find = NULL;
+static g_option_t *g_options = NULL;
+static int cmdfdin, cmdfdout;
 
 static int amindexd_debug = 0;
 
@@ -87,35 +85,38 @@ static REMOVE_ITEM *uncompress_remove = NULL;
 static am_feature_t *our_features = NULL;
 static am_feature_t *their_features = NULL;
 
-static REMOVE_ITEM *remove_files P((REMOVE_ITEM *));
-static char *uncompress_file P((char *, char **));
-static int process_ls_dump P((char *, DUMP_ITEM *, int, char **));
+static REMOVE_ITEM *remove_files(REMOVE_ITEM *);
+static char *uncompress_file(char *, char **);
+static int process_ls_dump(char *, DUMP_ITEM *, int, char **);
 
- /* XXX this is a hack to make sure the printf-ish output buffer
-    for lreply and friends is big enough for long label strings.
-    Should go away if someone institutes a more fundamental fix 
-    for that problem. */
- static int str_buffer_size = STR_SIZE;
+static size_t reply_buffer_size = 1;
+static char *reply_buffer = NULL;
+static char *amandad_auth = NULL;
 
-static void reply P((int, char *, ...))
+static void reply(int, char *, ...)
     __attribute__ ((format (printf, 2, 3)));
-static void lreply P((int, char *, ...))
+static void lreply(int, char *, ...)
     __attribute__ ((format (printf, 2, 3)));
-static void fast_lreply P((int, char *, ...))
+static void fast_lreply(int, char *, ...)
     __attribute__ ((format (printf, 2, 3)));
-static int is_dump_host_valid P((char *));
-static int is_disk_valid P((char *));
-static int is_config_valid P((char *));
-static int build_disk_table P((void));
-static int disk_history_list P((void));
-static int is_dir_valid_opaque P((char *));
-static int opaque_ls P((char *, int));
-static int tapedev_is P((void));
-static int are_dumps_compressed P((void));
-int main P((int, char **));
-
-static REMOVE_ITEM *remove_files(remove)
-REMOVE_ITEM *remove;
+static int is_dump_host_valid(char *);
+static int is_disk_valid(char *);
+static int is_config_valid(char *);
+static int build_disk_table(void);
+static int disk_history_list(void);
+static int is_dir_valid_opaque(char *);
+static int opaque_ls(char *, int);
+static void opaque_ls_one (DIR_ITEM *dir_item, am_feature_e marshall_feature,
+                            int recursive);
+static int tapedev_is(void);
+static int are_dumps_compressed(void);
+static char *amindexd_nicedate (char *datestamp);
+static int cmp_date (const char *date1, const char *date2);
+int main(int, char **);
+
+static REMOVE_ITEM *
+remove_files(
+    REMOVE_ITEM *remove)
 {
     REMOVE_ITEM *prev;
 
@@ -131,15 +132,16 @@ REMOVE_ITEM *remove;
     return remove;
 }
 
-static char *uncompress_file(filename_gz, emsg)
-char *filename_gz;
-char **emsg;
+static char *
+uncompress_file(
+    char *     filename_gz,
+    char **    emsg)
 {
     char *cmd = NULL;
     char *filename = NULL;
     struct stat stat_filename;
     int result;
-    int len;
+    size_t len;
 
     filename = stralloc(filename_gz);
     len = strlen(filename);
@@ -152,21 +154,36 @@ char **emsg;
     /* uncompress the file */
     result=stat(filename,&stat_filename);
     if(result==-1 && errno==ENOENT) {          /* file does not exist */
+       struct stat statbuf;
        REMOVE_ITEM *remove_file;
+
+       /*
+        * Check that compressed file exists manually.
+        */
+       if (stat(filename_gz, &statbuf) < 0) {
+           *emsg = newvstralloc(*emsg, "Compressed file '",
+                               filename_gz,
+                               "' is inaccessable: ",
+                               strerror(errno),
+                               NULL);
+           dbprintf(("%s\n",*emsg));
+           amfree(filename);
+           return NULL;
+       }
+
        cmd = vstralloc(UNCOMPRESS_PATH,
 #ifdef UNCOMPRESS_OPT
                        " ", UNCOMPRESS_OPT,
 #endif
                        " \'", filename_gz, "\'",
                        " 2>/dev/null",
-                       " | sort",
+                       " | (LC_ALL=C; export LC_ALL ; sort) ",
                        " > ", "\'", filename, "\'",
                        NULL);
        dbprintf(("%s: uncompress command: %s\n",
                  debug_prefix_time(NULL), cmd));
-       if (system(cmd)!=0) {
-           amfree(*emsg);
-           *emsg = vstralloc("\"", cmd, "\" failed", NULL);
+       if (system(cmd) != 0) {
+           *emsg = newvstralloc(*emsg, "\"", cmd, "\" failed", NULL);
            unlink(filename);
            errno = -1;
            amfree(filename);
@@ -175,7 +192,7 @@ char **emsg;
        }
 
        /* add at beginning */
-       remove_file = (REMOVE_ITEM *)alloc(sizeof(REMOVE_ITEM));
+       remove_file = (REMOVE_ITEM *)alloc(SIZEOF(REMOVE_ITEM));
        remove_file->filename = stralloc(filename);
        remove_file->next = uncompress_remove;
        uncompress_remove = remove_file;
@@ -186,8 +203,6 @@ char **emsg;
            amfree(filename);
            amfree(cmd);
            return NULL;
-    } else {
-       /* already uncompressed */
     }
     amfree(cmd);
     return filename;
@@ -195,11 +210,12 @@ char **emsg;
 
 /* find all matching entries in a dump listing */
 /* return -1 if error */
-static int process_ls_dump(dir, dump_item, recursive, emsg)
-char *dir;
-DUMP_ITEM *dump_item;
-int  recursive;
-char **emsg;
+static int
+process_ls_dump(
+    char *     dir,
+    DUMP_ITEM *        dump_item,
+    int                recursive,
+    char **    emsg)
 {
     char *line = NULL;
     char *old_line = NULL;
@@ -209,7 +225,7 @@ char **emsg;
     FILE *fp;
     char *s;
     int ch;
-    int len_dir_slash;
+    size_t len_dir_slash;
 
     if (strcmp(dir, "/") == 0) {
        dir_slash = stralloc(dir);
@@ -235,28 +251,31 @@ char **emsg;
 
     len_dir_slash=strlen(dir_slash);
 
-    for(; (line = agets(fp)) != NULL; free(line)) {
-       if(strncmp(dir_slash, line, len_dir_slash) == 0) {
-           if(!recursive) {
-               s = line + len_dir_slash;
-               ch = *s++;
-               while(ch && ch != '/') ch = *s++;/* find end of the file name */
-               if(ch == '/') {
+    while ((line = agets(fp)) != NULL) {
+       if (line[0] != '\0') {
+           if(strncmp(dir_slash, line, len_dir_slash) == 0) {
+               if(!recursive) {
+                   s = line + len_dir_slash;
                    ch = *s++;
+                   while(ch && ch != '/')
+                       ch = *s++;/* find end of the file name */
+                   if(ch == '/') {
+                       ch = *s++;
+                   }
+                   s[-1] = '\0';
+               }
+               if(old_line == NULL || strcmp(line, old_line) != 0) {
+                   add_dir_list_item(dump_item, line);
+                   amfree(old_line);
+                   old_line = line;
+                   line = NULL;
                }
-               s[-1] = '\0';
-           }
-           if(old_line == NULL || strcmp(line, old_line) != 0) {
-               add_dir_list_item(dump_item, line);
-               amfree(old_line);
-               old_line = line;
-               line = NULL;
            }
        }
+       /*@i@*/ amfree(line);
     }
     afclose(fp);
-    amfree(old_line);
-    amfree(line);
+    /*@i@*/ amfree(old_line);
     amfree(filename);
     amfree(dir_slash);
     return 0;
@@ -266,16 +285,25 @@ char **emsg;
 printf_arglist_function1(static void reply, int, n, char *, fmt)
 {
     va_list args;
-    char *buf;
+    int len;
 
-    buf = alloc(str_buffer_size);
+    if(!reply_buffer)
+       reply_buffer = alloc(reply_buffer_size);
 
-    arglist_start(args, fmt);
-    snprintf(buf, str_buffer_size, "%03d ", n);
-    vsnprintf(buf+4, str_buffer_size-4, fmt, args);
-    arglist_end(args);
+    while(1) {
+       arglist_start(args, fmt);
+       len = vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
+       arglist_end(args);
 
-    if (printf("%s\r\n", buf) < 0)
+       if (len > -1 && (size_t)len < reply_buffer_size)
+           break;
+
+       reply_buffer_size *= 2;
+       amfree(reply_buffer);
+       reply_buffer = alloc(reply_buffer_size);
+    }
+
+    if (printf("%03d %s\r\n", n, reply_buffer) < 0)
     {
        dbprintf(("%s: ! error %d (%s) in printf\n",
                  debug_prefix_time(NULL), errno, strerror(errno)));
@@ -289,26 +317,39 @@ printf_arglist_function1(static void reply, int, n, char *, fmt)
        uncompress_remove = remove_files(uncompress_remove);
        exit(1);
     }
-    dbprintf(("%s: < %s\n", debug_prefix_time(NULL), buf));
-    amfree(buf);
+    dbprintf(("%s: < %03d %s\n", debug_prefix_time(NULL), n, reply_buffer));
 }
 
-static void lreply_backend(int flush, int n, char *fmt, va_list args) {
-    char *buf;
+/* send one line of a multi-line response */
+printf_arglist_function1(static void lreply, int, n, char *, fmt)
+{
+    va_list args;
+    int len;
+
+    if(!reply_buffer)
+       reply_buffer = alloc(reply_buffer_size);
 
-    buf = alloc(str_buffer_size);
+    while(1) {
+       arglist_start(args, fmt);
+       len = vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
+       arglist_end(args);
 
-    snprintf(buf, str_buffer_size, "%03d-", n);
-    vsnprintf(buf+4, str_buffer_size-4, fmt, args);
+       if (len > -1 && (size_t)len < reply_buffer_size)
+           break;
 
-    if (printf("%s\r\n", buf) < 0)
+       reply_buffer_size *= 2;
+       amfree(reply_buffer);
+       reply_buffer = alloc(reply_buffer_size);
+    }
+
+    if (printf("%03d-%s\r\n", n, reply_buffer) < 0)
     {
        dbprintf(("%s: ! error %d (%s) in printf\n",
                  debug_prefix_time(NULL), errno, strerror(errno)));
        uncompress_remove = remove_files(uncompress_remove);
        exit(1);
     }
-    if (flush && fflush(stdout) != 0)
+    if (fflush(stdout) != 0)
     {
        dbprintf(("%s: ! error %d (%s) in fflush\n",
                  debug_prefix_time(NULL), errno, strerror(errno)));
@@ -316,30 +357,41 @@ static void lreply_backend(int flush, int n, char *fmt, va_list args) {
        exit(1);
     }
 
-    dbprintf(("%s: < %s\n", debug_prefix_time(NULL), buf));
-    amfree(buf);
+    dbprintf(("%s: < %03d-%s\n", debug_prefix_time(NULL), n, reply_buffer));
+
 }
 
 /* send one line of a multi-line response */
-printf_arglist_function1(static void lreply, int, n, char *, fmt)
+printf_arglist_function1(static void fast_lreply, int, n, char *, fmt)
 {
     va_list args;
+    int len;
 
-    arglist_start(args, fmt);
-    lreply_backend(1, n, fmt, args);
-    arglist_end(args);
+    if(!reply_buffer)
+       reply_buffer = alloc(reply_buffer_size);
 
-}
+    while(1) {
+       arglist_start(args, fmt);
+       len = vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
+       arglist_end(args);
 
-/* send one line of a multi-line response */
-printf_arglist_function1(static void fast_lreply, int, n, char *, fmt)
-{
-    va_list args;
+       if (len > -1 && (size_t)len < reply_buffer_size)
+           break;
 
-    arglist_start(args, fmt);
-    lreply_backend(0, n, fmt, args);
-    arglist_end(args);
+       reply_buffer_size *= 2;
+       amfree(reply_buffer);
+       reply_buffer = alloc(reply_buffer_size);
+    }
 
+    if (printf("%03d-%s\r\n", n, reply_buffer) < 0)
+    {
+       dbprintf(("%s: ! error %d (%s) in printf\n",
+                 debug_prefix_time(NULL), errno, strerror(errno)));
+       uncompress_remove = remove_files(uncompress_remove);
+       exit(1);
+    }
+
+    dbprintf(("%s: < %03d-%s\n", debug_prefix_time(NULL), n, reply_buffer));
 }
 
 /* see if hostname is valid */
@@ -347,8 +399,9 @@ printf_arglist_function1(static void fast_lreply, int, n, char *, fmt)
 /* also do a security check on the requested dump hostname */
 /* to restrict access to index records if required */
 /* return -1 if not okay */
-static int is_dump_host_valid(host)
-char *host;
+static int
+is_dump_host_valid(
+    char *     host)
 {
     struct stat dir_stat;
     char *fn;
@@ -392,30 +445,40 @@ char *host;
 }
 
 
-static int is_disk_valid(disk)
-char *disk;
+static int
+is_disk_valid(
+    char *disk)
 {
     char *fn;
     struct stat dir_stat;
     disk_t *idisk;
+    char *qdisk;
 
-    if (config_name == NULL || dump_hostname == NULL) {
+    if (config_name == NULL) {
        reply(501, "Must set config,host before setting disk.");
        return -1;
     }
+    else if (dump_hostname == NULL) {
+       reply(501, "Must set host before setting disk.");
+       return -1;
+    }
 
     /* check that the config actually handles that disk */
     idisk = lookup_disk(dump_hostname, disk);
     if(idisk == NULL) {
-       reply(501, "Disk %s:%s is not in your disklist.", dump_hostname, disk);
+       qdisk = quote_string(disk);
+       reply(501, "Disk %s:%s is not in your disklist.", dump_hostname, qdisk);
+       amfree(qdisk);
        return -1;
     }
 
     /* assume an index dir already */
     fn = getindexfname(dump_hostname, disk, NULL, 0);
     if (stat (fn, &dir_stat) != 0 || !S_ISDIR(dir_stat.st_mode)) {
-       reply(501, "No index records for disk: %s. Invalid?", disk);
+       qdisk = quote_string(disk);
+       reply(501, "No index records for disk: %s. Invalid?", qdisk);
        amfree(fn);
+       amfree(qdisk);
        return -1;
     }
 
@@ -424,8 +487,9 @@ char *disk;
 }
 
 
-static int is_config_valid(config)
-char *config;
+static int
+is_config_valid(
+    char *     config)
 {
     char *conffile;
     char *conf_diskfile;
@@ -474,6 +538,8 @@ char *config;
     }
     amfree(conf_tapelist);
 
+    dbrename(config, DBG_SUBDIR_SERVER);
+
     output_find = find_dump(1, &disk_list);
     sort_find_result("DLKHpB", &output_find);
 
@@ -494,23 +560,32 @@ char *config;
 }
 
 
-static int build_disk_table()
+static int
+build_disk_table(void)
 {
-    char date[3 * NUM_STR_SIZE + 2 + 1];
-    long last_datestamp;
-    int last_filenum;
+    char *date;
+    char *last_timestamp;
+    off_t last_filenum;
     int last_level;
     int last_partnum;
     find_result_t *find_output;
 
-    if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
+    if (config_name == NULL) {
        reply(590, "Must set config,host,disk before building disk table");
        return -1;
     }
+    else if (dump_hostname == NULL) {
+       reply(590, "Must set host,disk before building disk table");
+       return -1;
+    }
+    else if (disk_name == NULL) {
+       reply(590, "Must set disk before building disk table");
+       return -1;
+    }
 
     clear_list();
-    last_datestamp = -1;
-    last_filenum = -1;
+    last_timestamp = NULL;
+    last_filenum = (off_t)-1;
     last_level = -1;
     last_partnum = -1;
     for(find_output = output_find;
@@ -529,71 +604,85 @@ static int build_disk_table()
             * for the same datestamp after we see a holding disk entry
             * (as indicated by a filenum of zero).
             */
-           if(find_output->datestamp == last_datestamp &&
+           if(last_timestamp &&
+              strcmp(find_output->timestamp, last_timestamp) == 0 &&
               find_output->level == last_level && 
               partnum == last_partnum && last_filenum == 0) {
                continue;
            }
-           last_datestamp = find_output->datestamp;
+           last_timestamp = find_output->timestamp;
            last_filenum = find_output->filenum;
            last_level = find_output->level;
            last_partnum = partnum;
-           snprintf(date, sizeof(date), "%04d-%02d-%02d",
-                       find_output->datestamp/10000,
-                       (find_output->datestamp/100) %100,
-                       find_output->datestamp %100);
+           date = amindexd_nicedate(find_output->timestamp);
            add_dump(date, find_output->level, find_output->label, 
                     find_output->filenum, partnum);
-           dbprintf(("%s: - %s %d %s %d %d\n",
+           dbprintf(("%s: - %s %d %s " OFF_T_FMT " %d\n",
                     debug_prefix_time(NULL), date, find_output->level, 
-                    find_output->label, find_output->filenum, partnum));
+                    find_output->label,
+                    (OFF_T_FMT_TYPE)find_output->filenum,
+                    partnum));
        }
     }
     return 0;
 }
 
 
-static int disk_history_list()
+static int
+disk_history_list(void)
 {
     DUMP_ITEM *item;
+    char date[20];
 
-    if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
+    if (config_name == NULL) {
        reply(502, "Must set config,host,disk before listing history");
        return -1;
     }
+    else if (dump_hostname == NULL) {
+       reply(502, "Must set host,disk before listing history");
+       return -1;
+    }
+    else if (disk_name == NULL) {
+       reply(502, "Must set disk before listing history");
+       return -1;
+    }
 
-    lreply(200, " Dump history for config \"%s\" host \"%s\" disk \"%s\"",
-         config_name, dump_hostname, disk_name);
+    lreply(200, " Dump history for config \"%s\" host \"%s\" disk %s",
+         config_name, dump_hostname, qdisk_name);
 
     for (item=first_dump(); item!=NULL; item=next_dump(item)){
         char *tapelist_str = marshal_tapelist(item->tapes, 1);
 
+       strncpy(date, item->date, 20);
+       date[19] = '\0';
+       if(!am_has_feature(their_features,fe_amrecover_timestamp))
+           date[10] = '\0';
+
        if(am_has_feature(their_features, fe_amindexd_marshall_in_DHST)){
-           str_buffer_size = strlen(item->date) + NUM_STR_SIZE +
-                             strlen(tapelist_str) + 9;
-           lreply(201, " %s %d %s", item->date, item->level, tapelist_str);
+           lreply(201, " %s %d %s", date, item->level, tapelist_str);
        }
        else{
-           str_buffer_size = strlen(item->date) + NUM_STR_SIZE +
-                             strlen(tapelist_str) + NUM_STR_SIZE + 9;
-           lreply(201, " %s %d %s %d", item->date, item->level, tapelist_str,
-              item->file);
+           lreply(201, " %s %d %s " OFF_T_FMT, date, item->level,
+               tapelist_str, (OFF_T_FMT_TYPE)item->file);
        }
-       str_buffer_size = STR_SIZE;
+       amfree(tapelist_str);
     }
 
-    reply(200, "Dump history for config \"%s\" host \"%s\" disk \"%s\"",
-         config_name, dump_hostname, disk_name);
+    reply(200, "Dump history for config \"%s\" host \"%s\" disk %s",
+         config_name, dump_hostname, qdisk_name);
 
     return 0;
 }
 
 
-/* is the directory dir backed up - dir assumed complete relative to
-   disk mount point */
+/*
+ * is the directory dir backed up - dir assumed complete relative to
+ * disk mount point
+ */
 /* opaque version of command */
-static int is_dir_valid_opaque(dir)
-char *dir;
+static int
+is_dir_valid_opaque(
+    char *dir)
 {
     DUMP_ITEM *item;
     char *line = NULL;
@@ -602,21 +691,29 @@ char *dir;
     char *ldir = NULL;
     char *filename_gz = NULL;
     char *filename = NULL;
-    int ldir_len;
+    size_t ldir_len;
     static char *emsg = NULL;
 
     if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
        reply(502, "Must set config,host,disk before asking about directories");
        return -1;
     }
-    if (target_date == NULL) {
+    else if (dump_hostname == NULL) {
+       reply(502, "Must set host,disk before asking about directories");
+       return -1;
+    }
+    else if (disk_name == NULL) {
+       reply(502, "Must set disk before asking about directories");
+       return -1;
+    }
+    else if (target_date == NULL) {
        reply(502, "Must set date before asking about directories");
        return -1;
     }
 
     /* scan through till we find first dump on or before date */
     for (item=first_dump(); item!=NULL; item=next_dump(item))
-       if (strcmp(item->date, target_date) <= 0)
+       if (cmp_date(item->date, target_date) <= 0)
            break;
 
     if (item == NULL)
@@ -655,6 +752,8 @@ char *dir;
            return -1;
        }
        for(; (line = agets(fp)) != NULL; free(line)) {
+           if (line[0] == '\0')
+               continue;
            if (strncmp(line, ldir, ldir_len) != 0) {
                continue;                       /* not found yet */
            }
@@ -679,13 +778,14 @@ char *dir;
     return -1;
 }
 
-static int opaque_ls(dir,recursive)
-char *dir;
-int  recursive;
+static int
+opaque_ls(
+    char *     dir,
+    int                recursive)
 {
     DUMP_ITEM *dump_item;
     DIR_ITEM *dir_item;
-    int last_level;
+    int level, last_level;
     static char *emsg = NULL;
     am_feature_e marshall_feature;
 
@@ -697,18 +797,26 @@ int  recursive;
 
     clear_dir_list();
 
-    if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
+    if (config_name == NULL) {
        reply(502, "Must set config,host,disk before listing a directory");
        return -1;
     }
-    if (target_date == NULL) {
+    else if (dump_hostname == NULL) {
+       reply(502, "Must set host,disk before listing a directory");
+       return -1;
+    }
+    else if (disk_name == NULL) {
+       reply(502, "Must set disk before listing a directory");
+       return -1;
+    }
+    else if (target_date == NULL) {
        reply(502, "Must set date before listing a directory");
        return -1;
     }
 
     /* scan through till we find first dump on or before date */
     for (dump_item=first_dump(); dump_item!=NULL; dump_item=next_dump(dump_item))
-       if (strcmp(dump_item->date, target_date) <= 0)
+       if (cmp_date(dump_item->date, target_date) <= 0)
            break;
 
     if (dump_item == NULL)
@@ -742,49 +850,23 @@ int  recursive;
 
     /* return the information to the caller */
     lreply(200, " Opaque list of %s", dir);
-        for (dir_item = get_dir_list(); dir_item != NULL; 
-             dir_item = dir_item->next) {
-            char *tapelist_str;
-
-            if (!am_has_feature(their_features, marshall_feature) &&
-                (num_entries(dir_item->dump->tapes) > 1 ||
-                dir_item->dump->tapes->numfiles > 1)) {
-                fast_lreply(501, " ERROR: Split dumps not supported"
-                            " with old version of amrecover.");
-                break;
-            } else {
-                if (am_has_feature(their_features, marshall_feature)) {
-                    tapelist_str = marshal_tapelist(dir_item->dump->tapes, 1);
-                } else {
-                    tapelist_str = dir_item->dump->tapes->label;
-                }
-                
-                if((!recursive && am_has_feature(their_features,
-                                                 fe_amindexd_fileno_in_OLSD))
-                   ||
-                   (recursive && am_has_feature(their_features,
-                                                fe_amindexd_fileno_in_ORLD))) {
-                    str_buffer_size = strlen(dir_item->dump->date) +
-                        NUM_STR_SIZE + strlen(tapelist_str) + 
-                        strlen(dir_item->path) + NUM_STR_SIZE + 9;
-                    fast_lreply(201, " %s %d %s %d %s",
-                                dir_item->dump->date, dir_item->dump->level,
-                                tapelist_str, dir_item->dump->file,
-                                dir_item->path);
-                }
-                else {
-                    str_buffer_size = strlen(dir_item->dump->date) +
-                        NUM_STR_SIZE + strlen(tapelist_str) +
-                        strlen(dir_item->path) + 9;
-                    fast_lreply(201, " %s %d %s %s",
-                                dir_item->dump->date, dir_item->dump->level,
-                                tapelist_str, dir_item->path);
-                }
-               if(am_has_feature(their_features, marshall_feature)) {
-                   amfree(tapelist_str);
+    for(level=0; level<=9; level++) {
+       for (dir_item = get_dir_list(); dir_item != NULL; 
+            dir_item = dir_item->next) {
+
+           if(dir_item->dump->level == level) {
+               if (!am_has_feature(their_features, marshall_feature) &&
+                   (num_entries(dir_item->dump->tapes) > 1 ||
+                   dir_item->dump->tapes->numfiles > 1)) {
+                   fast_lreply(501, " ERROR: Split dumps not supported"
+                               " with old version of amrecover.");
+                   break;
                }
-                str_buffer_size = STR_SIZE;
-            }
+               else {
+                   opaque_ls_one(dir_item, marshall_feature, recursive);
+               }
+           }
+       }
     }
     reply(200, " Opaque list of %s", dir);
 
@@ -792,10 +874,57 @@ int  recursive;
     return 0;
 }
 
+void opaque_ls_one(
+    DIR_ITEM *  dir_item,
+    am_feature_e marshall_feature,
+    int                 recursive)
+{
+   char date[20];
+   char *tapelist_str;
+    char *qpath;
 
-/* returns the value of changer or tapedev from the amanda.conf file if set,
-   otherwise reports an error */
-static int tapedev_is()
+    if (am_has_feature(their_features, marshall_feature)) {
+       tapelist_str = marshal_tapelist(dir_item->dump->tapes, 1);
+    } else {
+       tapelist_str = dir_item->dump->tapes->label;
+    }
+
+    strncpy(date, dir_item->dump->date, 20);
+    date[19] = '\0';
+    if(!am_has_feature(their_features,fe_amrecover_timestamp))
+       date[10] = '\0';
+
+    qpath = quote_string(dir_item->path);
+    if((!recursive && am_has_feature(their_features,
+                                    fe_amindexd_fileno_in_OLSD)) ||
+       (recursive && am_has_feature(their_features,
+                                   fe_amindexd_fileno_in_ORLD))) {
+       fast_lreply(201, " %s %d %s " OFF_T_FMT " %s",
+                   date,
+                   dir_item->dump->level,
+                   tapelist_str,
+                   (OFF_T_FMT_TYPE)dir_item->dump->file,
+                   qpath);
+    }
+    else {
+
+       fast_lreply(201, " %s %d %s %s",
+                   date, dir_item->dump->level,
+                   tapelist_str, qpath);
+    }
+    amfree(qpath);
+    if(am_has_feature(their_features, marshall_feature)) {
+       amfree(tapelist_str);
+    }
+}
+
+/*
+ * returns the value of changer or tapedev from the amanda.conf file if set,
+ * otherwise reports an error
+ */
+
+static int
+tapedev_is(void)
 {
     char *result;
 
@@ -838,24 +967,34 @@ static int tapedev_is()
 
 
 /* returns YES if dumps for disk are compressed, NO if not */
-static int are_dumps_compressed()
+static int
+are_dumps_compressed(void)
 {
     disk_t *diskp;
 
     /* check state okay to do this */
-    if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
+    if (config_name == NULL) {
        reply(501, "Must set config,host,disk name before asking about dumps.");
        return -1;
     }
+    else if (dump_hostname == NULL) {
+       reply(501, "Must set host,disk name before asking about dumps.");
+       return -1;
+    }
+    else if (disk_name == NULL) {
+       reply(501, "Must set disk name before asking about dumps.");
+       return -1;
+    }
 
     /* now go through the list of disks and find which have indexes */
-    for (diskp = disk_list.head; diskp != NULL; diskp = diskp->next)
+    for (diskp = disk_list.head; diskp != NULL; diskp = diskp->next) {
        if ((strcasecmp(diskp->host->hostname, dump_hostname) == 0)
-           && (strcmp(diskp->name, disk_name) == 0))
+               && (strcmp(diskp->name, disk_name) == 0)) {
            break;
+       }
+    }
 
-    if (diskp == NULL)
-    {
+    if (diskp == NULL) {
        reply(501, "Couldn't find host/disk in disk file.");
        return -1;
     }
@@ -869,9 +1008,10 @@ static int are_dumps_compressed()
     return 0;
 }
 
-int main(argc, argv)
-int argc;
-char **argv;
+int
+main(
+    int                argc,
+    char **    argv)
 {
     char *line = NULL, *part = NULL;
     char *s, *fp;
@@ -880,14 +1020,14 @@ char **argv;
     socklen_t socklen;
     struct sockaddr_in his_addr;
     struct hostent *his_name;
-    char *arg;
+    char *arg = NULL;
     char *cmd;
-    int len;
+    size_t len;
     int user_validated = 0;
     char *errstr = NULL;
-    char *pgm = "amindexd";                    /* in case argv[0] is not set */
+    char *pgm = "amindexd";            /* in case argv[0] is not set */
 
-    safe_fd(-1, 0);
+    safe_fd(DATA_FD_OFFSET, 2);
     safe_cd();
 
     /*
@@ -916,16 +1056,19 @@ char **argv;
     if(geteuid() == 0) {
        if(client_uid == (uid_t) -1) {
            error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
+           /*NOTREACHED*/
        }
 
+       /*@ignore@*/
        initgroups(CLIENT_LOGIN, client_gid);
+       /*@end@*/
        setgid(client_gid);
        setuid(client_uid);
     }
 
 #endif /* FORCE_USERID */
 
-    dbopen();
+    dbopen(DBG_SUBDIR_SERVER);
     dbprintf(("%s: version %s\n", get_pname(), version()));
 
     if(argv == NULL) {
@@ -955,6 +1098,21 @@ char **argv;
        argv++;
     }
 
+    if(argc > 0 && strcmp(*argv, "amandad") == 0) {
+       from_amandad = 1;
+       argc--;
+       argv++;
+       if(argc > 0) {
+           amandad_auth = *argv;
+           argc--;
+           argv++;
+       }
+    }
+    else {
+       from_amandad = 0;
+       safe_fd(-1, 0);
+    }
+
     if (argc > 0) {
        config_name = stralloc(*argv);
        config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
@@ -962,9 +1120,11 @@ char **argv;
        argv++;
     }
 
-    if(gethostname(local_hostname, sizeof(local_hostname)-1) == -1)
+    if(gethostname(local_hostname, SIZEOF(local_hostname)-1) == -1) {
        error("gethostname: %s", strerror(errno));
-    local_hostname[sizeof(local_hostname)-1] = '\0';
+       /*NOTREACHED*/
+    }
+    local_hostname[SIZEOF(local_hostname)-1] = '\0';
 
     /* now trim domain off name */
     s = local_hostname;
@@ -972,47 +1132,95 @@ char **argv;
     while(ch && ch != '.') ch = *s++;
     s[-1] = '\0';
 
-    if(amindexd_debug) {
-       /*
-        * Fake the remote address as the local address enough to get
-        * through the security check.
-        */
-       his_name = gethostbyname(local_hostname);
-       if(his_name == NULL) {
-           error("gethostbyname(%s) failed\n", local_hostname);
+
+    if(from_amandad == 0) {
+       if(amindexd_debug) {
+           /*
+            * Fake the remote address as the local address enough to get
+            * through the security check.
+            */
+           his_name = gethostbyname(local_hostname);
+           if(his_name == NULL) {
+               error("gethostbyname(%s) failed\n", local_hostname);
+                /*NOTREACHED*/
+           }
+           assert((sa_family_t)his_name->h_addrtype == (sa_family_t)AF_INET);
+           his_addr.sin_family = (sa_family_t)his_name->h_addrtype;
+           his_addr.sin_port = (in_port_t)htons(0);
+           memcpy((void *)&his_addr.sin_addr.s_addr,
+                  (void *)his_name->h_addr_list[0], 
+                   (size_t)his_name->h_length);
+       } else {
+           /* who are we talking to? */
+           socklen = sizeof (his_addr);
+           if (getpeername(0, (struct sockaddr *)&his_addr, &socklen) == -1)
+               error("getpeername: %s", strerror(errno));
        }
-       assert(his_name->h_addrtype == AF_INET);
-       his_addr.sin_family = his_name->h_addrtype;
-       his_addr.sin_port = htons(0);
-       memcpy((char *)&his_addr.sin_addr.s_addr,
-              (char *)his_name->h_addr_list[0], his_name->h_length);
-    } else {
-       /* who are we talking to? */
-       socklen = sizeof (his_addr);
-       if (getpeername(0, (struct sockaddr *)&his_addr, &socklen) == -1)
-           error("getpeername: %s", strerror(errno));
-    }
-    if (his_addr.sin_family != AF_INET || ntohs(his_addr.sin_port) == 20)
-    {
-       error("connection rejected from %s family %d port %d",
-             inet_ntoa(his_addr.sin_addr), his_addr.sin_family,
-             htons(his_addr.sin_port));
+       if ((his_addr.sin_family != (sa_family_t)AF_INET)
+               || (ntohs(his_addr.sin_port) == 20)) {
+           error("connection rejected from %s family %d port %d",
+                 inet_ntoa(his_addr.sin_addr), his_addr.sin_family,
+                 htons(his_addr.sin_port));
+           /*NOTREACHED*/
+       }
+       if ((his_name = gethostbyaddr((char *)&(his_addr.sin_addr),
+                                     sizeof(his_addr.sin_addr),
+                                     AF_INET)) == NULL) {
+           error("gethostbyaddr(%s): hostname lookup failed",
+                 inet_ntoa(his_addr.sin_addr));
+           /*NOTREACHED*/
+       }
+       fp = s = stralloc(his_name->h_name);
+       ch = *s++;
+       while(ch && ch != '.') ch = *s++;
+       s[-1] = '\0';
+       remote_hostname = newstralloc(remote_hostname, fp);
+       s[-1] = (char)ch;
+       amfree(fp);
     }
-    if ((his_name = gethostbyaddr((char *)&(his_addr.sin_addr),
-                                 sizeof(his_addr.sin_addr),
-                                 AF_INET)) == NULL) {
-       error("gethostbyaddr(%s): hostname lookup failed",
-             inet_ntoa(his_addr.sin_addr));
+    else {
+       cmdfdout  = DATA_FD_OFFSET + 0;
+       cmdfdin   = DATA_FD_OFFSET + 1;
+
+       /* read the REQ packet */
+       for(; (line = agets(stdin)) != NULL; free(line)) {
+#define sc "OPTIONS "
+           if(strncmp(line, sc, sizeof(sc)-1) == 0) {
+#undef sc
+               g_options = parse_g_options(line+8, 1);
+               if(!g_options->hostname) {
+                   g_options->hostname = alloc(MAX_HOSTNAME_LENGTH+1);
+                   gethostname(g_options->hostname, MAX_HOSTNAME_LENGTH);
+                   g_options->hostname[MAX_HOSTNAME_LENGTH] = '\0';
+               }
+           }
+       }
+       amfree(line);
+
+       if(amandad_auth && g_options->auth) {
+           if(strcasecmp(amandad_auth, g_options->auth) != 0) {
+               printf("ERROR recover program ask for auth=%s while amindexd is configured for '%s'\n",
+                      g_options->auth, amandad_auth);
+               error("amindexd: ERROR recover program ask for auth=%s while amindexd is configured for '%s'",
+                     g_options->auth, amandad_auth);
+               /*NOTREACHED*/
+           }
+       }
+       /* send the REP packet */
+       printf("CONNECT MESG %d\n", DATA_FD_OFFSET);
+       printf("\n");
+       fflush(stdin);
+       fflush(stdout);
+       if ((dup2(cmdfdout, fileno(stdout)) < 0)
+                || (dup2(cmdfdin, fileno(stdin)) < 0)) {
+           error("amandad: Failed to setup stdin or stdout");
+           /*NOTREACHED*/
+       }
     }
-    fp = s = his_name->h_name;
-    ch = *s++;
-    while(ch && ch != '.') ch = *s++;
-    s[-1] = '\0';
-    remote_hostname = newstralloc(remote_hostname, fp);
-    s[-1] = ch;
 
     /* clear these so we can detect when the have not been set by the client */
     amfree(dump_hostname);
+    amfree(qdisk_name);
     amfree(disk_name);
     amfree(target_date);
 
@@ -1026,11 +1234,12 @@ char **argv;
     reply(220, "%s AMANDA index server (%s) ready.", local_hostname,
          version());
 
+    user_validated = from_amandad;
+
     /* a real simple parser since there are only a few commands */
     while (1)
     {
        /* get a line from the client */
-       amfree(line);
        while(1) {
            if((part = agets(stdin)) == NULL) {
                if(errno != 0) {
@@ -1053,13 +1262,9 @@ char **argv;
                dbclose();
                return 1;               /* they hung up? */
            }
-           if(line) {
-               strappend(line, part);
-               amfree(part);
-           } else {
-               line = part;
-               part = NULL;
-           }
+           strappend(line, part);      /* Macro: line can be null */
+           amfree(part);
+
            if(amindexd_debug) {
                break;                  /* we have a whole line */
            }
@@ -1077,13 +1282,15 @@ char **argv;
 
        dbprintf(("%s: > %s\n", debug_prefix_time(NULL), line));
 
-       arg = NULL;
+       if (arg != NULL)
+           amfree(arg);
        s = line;
        ch = *s++;
 
        skip_whitespace(s, ch);
        if(ch == '\0') {
            reply(500, "Command not recognised/incorrect: %s", line);
+           amfree(line);
            continue;
        }
        cmd = s - 1;
@@ -1096,7 +1303,8 @@ char **argv;
            skip_whitespace(s, ch);             /* find the argument */
            if (ch) {
                arg = s-1;
-               skip_non_whitespace(s, ch);
+               skip_quoted_string(s, ch);
+               arg = unquote_string(arg);
            }
        }
 
@@ -1105,6 +1313,7 @@ char **argv;
            user_validated = check_security(&his_addr, arg, 0, &errstr);
            if(user_validated) {
                reply(200, "Access OK");
+               amfree(line);
                continue;
            }
        }
@@ -1117,6 +1326,7 @@ char **argv;
        }
 
        if (strcmp(cmd, "QUIT") == 0) {
+           amfree(line);
            break;
        } else if (strcmp(cmd, "HOST") == 0 && arg) {
            /* set host we are restoring */
@@ -1125,33 +1335,74 @@ char **argv;
            {
                dump_hostname = newstralloc(dump_hostname, arg);
                reply(200, "Dump host set to %s.", dump_hostname);
+               amfree(qdisk_name);             /* invalidate any value */
                amfree(disk_name);              /* invalidate any value */
            }
-           s[-1] = ch;
+           s[-1] = (char)ch;
+       } else if (strcmp(cmd, "LISTHOST") == 0) {
+           disk_t *disk, 
+                   *diskdup;
+           int nbhost = 0,
+                found = 0;
+           s[-1] = '\0';
+           if (config_name == NULL) {
+               reply(501, "Must set config before listhost");
+           }
+           else {
+               lreply(200, " List hosts for config %s", config_name);
+               for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
+                    found = 0;
+                   for (diskdup = disk_list.head; diskdup!=disk; diskdup = diskdup->next) {
+                       if(strcmp(diskdup->host->hostname, disk->host->hostname) == 0) {
+                          found = 1;
+                          break;
+                       }
+                    }
+                    if(!found){
+                       fast_lreply(201, " %s", disk->host->hostname);
+                        nbhost++;
+                    }
+               }
+               if(nbhost > 0) {
+                   reply(200, " List hosts for config %s", config_name);
+               }
+               else {
+                   reply(200, "No hosts for config %s", config_name);
+               }
+           }
+           s[-1] = (char)ch;
        } else if (strcmp(cmd, "DISK") == 0 && arg) {
            s[-1] = '\0';
            if (is_disk_valid(arg) != -1) {
                disk_name = newstralloc(disk_name, arg);
+               qdisk_name = quote_string(disk_name);
                if (build_disk_table() != -1) {
-                   reply(200, "Disk set to %s.", disk_name);
+                   reply(200, "Disk set to %s.", qdisk_name);
                }
            }
-           s[-1] = ch;
+           s[-1] = (char)ch;
        } else if (strcmp(cmd, "LISTDISK") == 0) {
+           char *qname;
            disk_t *disk;
            int nbdisk = 0;
            s[-1] = '\0';
-           if (config_name == NULL || dump_hostname == NULL) {
+           if (config_name == NULL) {
                reply(501, "Must set config, host before listdisk");
            }
+           else if (dump_hostname == NULL) {
+               reply(501, "Must set host before listdisk");
+           }
            else if(arg) {
                lreply(200, " List of disk for device %s on host %s", arg,
                       dump_hostname);
                for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
-                   if(strcmp(disk->host->hostname, dump_hostname) == 0 &&
-                      ((disk->device && strcmp(disk->device, arg) == 0) ||
-                       (!disk->device && strcmp(disk->name, arg) == 0))) {
-                       fast_lreply(201, " %s", disk->name);
+
+                   if (strcmp(disk->host->hostname, dump_hostname) == 0 &&
+                     ((disk->device && strcmp(disk->device, arg) == 0) ||
+                     (!disk->device && strcmp(disk->name, arg) == 0))) {
+                       qname = quote_string(disk->name);
+                       fast_lreply(201, " %s", qname);
+                       amfree(qname);
                        nbdisk++;
                    }
                }
@@ -1168,7 +1419,9 @@ char **argv;
                lreply(200, " List of disk for host %s", dump_hostname);
                for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
                    if(strcmp(disk->host->hostname, dump_hostname) == 0) {
-                       fast_lreply(201, " %s", disk->name);
+                       qname = quote_string(disk->name);
+                       fast_lreply(201, " %s", qname);
+                       amfree(qname);
                        nbdisk++;
                    }
                }
@@ -1179,7 +1432,7 @@ char **argv;
                    reply(200, "No disk for host %s", dump_hostname);
                }
            }
-           s[-1] = ch;
+           s[-1] = (char)ch;
        } else if (strcmp(cmd, "SCNF") == 0 && arg) {
            s[-1] = '\0';
            amfree(config_name);
@@ -1188,13 +1441,14 @@ char **argv;
            config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
            if (is_config_valid(arg) != -1) {
                amfree(dump_hostname);          /* invalidate any value */
+               amfree(qdisk_name);             /* invalidate any value */
                amfree(disk_name);              /* invalidate any value */
                reply(200, "Config set to %s.", config_name);
            } else {
                amfree(config_name);
                amfree(config_dir);
            }
-           s[-1] = ch;
+           s[-1] = (char)ch;
        } else if (strcmp(cmd, "FEATURES") == 0 && arg) {
            char *our_feature_string = NULL;
            char *their_feature_string = NULL;
@@ -1203,17 +1457,17 @@ char **argv;
            am_release_feature_set(their_features);
            our_features = am_init_feature_set();
            our_feature_string = am_feature_to_string(our_features);
-           their_feature_string = newstralloc(target_date, arg);
+           their_feature_string = newstralloc(their_feature_string, arg);
            their_features = am_string_to_feature(their_feature_string);
            reply(200, "FEATURES %s", our_feature_string);
            amfree(our_feature_string);
            amfree(their_feature_string);
-           s[-1] = ch;
+           s[-1] = (char)ch;
        } else if (strcmp(cmd, "DATE") == 0 && arg) {
            s[-1] = '\0';
            target_date = newstralloc(target_date, arg);
            reply(200, "Working date set to %s.", target_date);
-           s[-1] = ch;
+           s[-1] = (char)ch;
        } else if (strcmp(cmd, "DHST") == 0) {
            (void)disk_history_list();
        } else if (strcmp(cmd, "OISD") == 0 && arg) {
@@ -1223,7 +1477,7 @@ char **argv;
        } else if (strcmp(cmd, "OLSD") == 0 && arg) {
            (void)opaque_ls(arg,0);
        } else if (strcmp(cmd, "ORLD") == 0 && arg) {
-           (void)opaque_ls(arg,1);
+           (void)opaque_ls(arg, 1);
        } else if (strcmp(cmd, "TAPE") == 0) {
            (void)tapedev_is();
        } else if (strcmp(cmd, "DCMP") == 0) {
@@ -1232,12 +1486,57 @@ char **argv;
            *cmd_undo = cmd_undo_ch;    /* restore the command line */
            reply(500, "Command not recognised/incorrect: %s", cmd);
        }
+       amfree(line);
     }
-    amfree(line);
-
+    amfree(arg);
+    
     uncompress_remove = remove_files(uncompress_remove);
     free_find_result(&output_find);
     reply(200, "Good bye.");
     dbclose();
     return 0;
 }
+
+static char *
+amindexd_nicedate(
+    char *     datestamp)
+{
+    static char nice[20];
+    int year, month, day;
+    int hours, minutes, seconds;
+    char date[9], atime[7];
+    int  numdate, numtime;
+
+    strncpy(date, datestamp, 8);
+    date[8] = '\0';
+    numdate = atoi(date);
+    year  = numdate / 10000;
+    month = (numdate / 100) % 100;
+    day   = numdate % 100;
+
+    if(strlen(datestamp) <= 8) {
+       snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d",
+               year, month, day);
+    }
+    else {
+       strncpy(atime, &(datestamp[8]), 6);
+       atime[6] = '\0';
+       numtime = atoi(atime);
+       hours = numtime / 10000;
+       minutes = (numtime / 100) % 100;
+       seconds = numtime % 100;
+
+       snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d-%02d-%02d-%02d",
+               year, month, day, hours, minutes, seconds);
+    }
+
+    return nice;
+}
+
+static int
+cmp_date(
+    const char *       date1,
+    const char *       date2)
+{
+    return strncmp(date1, date2, strlen(date2));
+}