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