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