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