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