f721e2cf97bf18115be7d19b1debe43ce8da1cdb
[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.86 2006/03/09 16:51:41 martinea Exp $
28  *
29  * This is the server daemon part of the index client/server system.
30  * It is assumed that this is launched from inetd instead of being
31  * started as a daemon because it is not often used
32  */
33
34 /*
35 ** Notes:
36 ** - this server will do very little until it knows what Amanda config it
37 **   is to use.  Setting the config has the side effect of changing to the
38 **   index directory.
39 ** - XXX - I'm pretty sure the config directory name should have '/'s stripped
40 **   from it.  It is given to us by an unknown person via the network.
41 */
42
43 #include "amanda.h"
44 #include "conffile.h"
45 #include "diskfile.h"
46 #include "arglist.h"
47 #include "clock.h"
48 #include "version.h"
49 #include "amindex.h"
50 #include "disk_history.h"
51 #include "list_dir.h"
52 #include "logfile.h"
53 #include "token.h"
54 #include "find.h"
55 #include "tapefile.h"
56
57 #ifdef HAVE_NETINET_IN_SYSTM_H
58 #include <netinet/in_systm.h>
59 #endif
60
61 #ifdef HAVE_NETINET_IP_H
62 #include <netinet/ip.h>
63 #endif
64
65 #include <grp.h>
66
67 typedef struct REMOVE_ITEM
68 {
69     char *filename;
70     struct REMOVE_ITEM *next;
71 } REMOVE_ITEM;
72
73 /* state */
74 char local_hostname[MAX_HOSTNAME_LENGTH+1];     /* me! */
75 char *remote_hostname = NULL;                   /* the client */
76 char *dump_hostname = NULL;                     /* machine we are restoring */
77 char *disk_name;                                /* disk we are restoring */
78 char *target_date = NULL;
79 disklist_t disk_list;                           /* all disks in cur config */
80 find_result_t *output_find = NULL;
81
82 static int amindexd_debug = 0;
83
84 static REMOVE_ITEM *uncompress_remove = NULL;
85                                         /* uncompressed files to remove */
86
87 static am_feature_t *our_features = NULL;
88 static am_feature_t *their_features = NULL;
89
90 static REMOVE_ITEM *remove_files P((REMOVE_ITEM *));
91 static char *uncompress_file P((char *, char **));
92 static int process_ls_dump P((char *, DUMP_ITEM *, int, char **));
93
94  /* XXX this is a hack to make sure the printf-ish output buffer
95     for lreply and friends is big enough for long label strings.
96     Should go away if someone institutes a more fundamental fix 
97     for that problem. */
98  static int str_buffer_size = STR_SIZE;
99
100 static void reply P((int, char *, ...))
101     __attribute__ ((format (printf, 2, 3)));
102 static void lreply P((int, char *, ...))
103     __attribute__ ((format (printf, 2, 3)));
104 static void fast_lreply P((int, char *, ...))
105     __attribute__ ((format (printf, 2, 3)));
106 static int is_dump_host_valid P((char *));
107 static int is_disk_valid P((char *));
108 static int is_config_valid P((char *));
109 static int build_disk_table P((void));
110 static int disk_history_list P((void));
111 static int is_dir_valid_opaque P((char *));
112 static int opaque_ls P((char *, int));
113 static int tapedev_is P((void));
114 static int are_dumps_compressed P((void));
115 int main P((int, char **));
116
117 static REMOVE_ITEM *remove_files(remove)
118 REMOVE_ITEM *remove;
119 {
120     REMOVE_ITEM *prev;
121
122     while(remove) {
123         dbprintf(("%s: removing index file: %s\n",
124                   debug_prefix_time(NULL), remove->filename));
125         unlink(remove->filename);
126         amfree(remove->filename);
127         prev = remove;
128         remove = remove->next;
129         amfree(prev);
130     }
131     return remove;
132 }
133
134 static char *uncompress_file(filename_gz, emsg)
135 char *filename_gz;
136 char **emsg;
137 {
138     char *cmd = NULL;
139     char *filename = NULL;
140     struct stat stat_filename;
141     int result;
142     int len;
143
144     filename = stralloc(filename_gz);
145     len = strlen(filename);
146     if(len > 3 && strcmp(&(filename[len-3]),".gz")==0) {
147         filename[len-3]='\0';
148     } else if(len > 2 && strcmp(&(filename[len-2]),".Z")==0) {
149         filename[len-2]='\0';
150     }
151
152     /* uncompress the file */
153     result=stat(filename,&stat_filename);
154     if(result==-1 && errno==ENOENT) {           /* file does not exist */
155         REMOVE_ITEM *remove_file;
156         cmd = vstralloc(UNCOMPRESS_PATH,
157 #ifdef UNCOMPRESS_OPT
158                         " ", UNCOMPRESS_OPT,
159 #endif
160                         " \'", filename_gz, "\'",
161                         " 2>/dev/null",
162                         " | sort",
163                         " > ", "\'", filename, "\'",
164                         NULL);
165         dbprintf(("%s: uncompress command: %s\n",
166                   debug_prefix_time(NULL), cmd));
167         if (system(cmd)!=0) {
168             amfree(*emsg);
169             *emsg = vstralloc("\"", cmd, "\" failed", NULL);
170             unlink(filename);
171             errno = -1;
172             amfree(filename);
173             amfree(cmd);
174             return NULL;
175         }
176
177         /* add at beginning */
178         remove_file = (REMOVE_ITEM *)alloc(sizeof(REMOVE_ITEM));
179         remove_file->filename = stralloc(filename);
180         remove_file->next = uncompress_remove;
181         uncompress_remove = remove_file;
182     } else if(!S_ISREG((stat_filename.st_mode))) {
183             amfree(*emsg);
184             *emsg = vstralloc("\"", filename, "\" is not a regular file", NULL);
185             errno = -1;
186             amfree(filename);
187             amfree(cmd);
188             return NULL;
189     } else {
190         /* already uncompressed */
191     }
192     amfree(cmd);
193     return filename;
194 }
195
196 /* find all matching entries in a dump listing */
197 /* return -1 if error */
198 static int process_ls_dump(dir, dump_item, recursive, emsg)
199 char *dir;
200 DUMP_ITEM *dump_item;
201 int  recursive;
202 char **emsg;
203 {
204     char *line = NULL;
205     char *old_line = NULL;
206     char *filename = NULL;
207     char *filename_gz;
208     char *dir_slash = NULL;
209     FILE *fp;
210     char *s;
211     int ch;
212     int len_dir_slash;
213
214     if (strcmp(dir, "/") == 0) {
215         dir_slash = stralloc(dir);
216     } else {
217         dir_slash = stralloc2(dir, "/");
218     }
219
220     filename_gz = getindexfname(dump_hostname, disk_name, dump_item->date,
221                                 dump_item->level);
222     if((filename = uncompress_file(filename_gz, emsg)) == NULL) {
223         amfree(filename_gz);
224         amfree(dir_slash);
225         return -1;
226     }
227     amfree(filename_gz);
228
229     if((fp = fopen(filename,"r"))==0) {
230         amfree(*emsg);
231         *emsg = stralloc(strerror(errno));
232         amfree(dir_slash);
233         return -1;
234     }
235
236     len_dir_slash=strlen(dir_slash);
237
238     for(; (line = agets(fp)) != NULL; free(line)) {
239         if(strncmp(dir_slash, line, len_dir_slash) == 0) {
240             if(!recursive) {
241                 s = line + len_dir_slash;
242                 ch = *s++;
243                 while(ch && ch != '/') ch = *s++;/* find end of the file name */
244                 if(ch == '/') {
245                     ch = *s++;
246                 }
247                 s[-1] = '\0';
248             }
249             if(old_line == NULL || strcmp(line, old_line) != 0) {
250                 add_dir_list_item(dump_item, line);
251                 amfree(old_line);
252                 old_line = line;
253                 line = NULL;
254             }
255         }
256     }
257     afclose(fp);
258     amfree(old_line);
259     amfree(line);
260     amfree(filename);
261     amfree(dir_slash);
262     return 0;
263 }
264
265 /* send a 1 line reply to the client */
266 printf_arglist_function1(static void reply, int, n, char *, fmt)
267 {
268     va_list args;
269     char *buf;
270
271     buf = alloc(str_buffer_size);
272
273     arglist_start(args, fmt);
274     snprintf(buf, str_buffer_size, "%03d ", n);
275     vsnprintf(buf+4, str_buffer_size-4, fmt, args);
276     arglist_end(args);
277
278     if (printf("%s\r\n", buf) < 0)
279     {
280         dbprintf(("%s: ! error %d (%s) in printf\n",
281                   debug_prefix_time(NULL), errno, strerror(errno)));
282         uncompress_remove = remove_files(uncompress_remove);
283         exit(1);
284     }
285     if (fflush(stdout) != 0)
286     {
287         dbprintf(("%s: ! error %d (%s) in fflush\n",
288                   debug_prefix_time(NULL), errno, strerror(errno)));
289         uncompress_remove = remove_files(uncompress_remove);
290         exit(1);
291     }
292     dbprintf(("%s: < %s\n", debug_prefix_time(NULL), buf));
293     amfree(buf);
294 }
295
296 static void lreply_backend(int flush, int n, char *fmt, va_list args) {
297     char *buf;
298
299     buf = alloc(str_buffer_size);
300
301     snprintf(buf, str_buffer_size, "%03d-", n);
302     vsnprintf(buf+4, str_buffer_size-4, fmt, args);
303
304     if (printf("%s\r\n", buf) < 0)
305     {
306         dbprintf(("%s: ! error %d (%s) in printf\n",
307                   debug_prefix_time(NULL), errno, strerror(errno)));
308         uncompress_remove = remove_files(uncompress_remove);
309         exit(1);
310     }
311     if (flush && fflush(stdout) != 0)
312     {
313         dbprintf(("%s: ! error %d (%s) in fflush\n",
314                   debug_prefix_time(NULL), errno, strerror(errno)));
315         uncompress_remove = remove_files(uncompress_remove);
316         exit(1);
317     }
318
319     dbprintf(("%s: < %s\n", debug_prefix_time(NULL), buf));
320     amfree(buf);
321 }
322
323 /* send one line of a multi-line response */
324 printf_arglist_function1(static void lreply, int, n, char *, fmt)
325 {
326     va_list args;
327
328     arglist_start(args, fmt);
329     lreply_backend(1, n, fmt, args);
330     arglist_end(args);
331
332 }
333
334 /* send one line of a multi-line response */
335 printf_arglist_function1(static void fast_lreply, int, n, char *, fmt)
336 {
337     va_list args;
338
339     arglist_start(args, fmt);
340     lreply_backend(0, n, fmt, args);
341     arglist_end(args);
342
343 }
344
345 /* see if hostname is valid */
346 /* valid is defined to be that there is an index directory for it */
347 /* also do a security check on the requested dump hostname */
348 /* to restrict access to index records if required */
349 /* return -1 if not okay */
350 static int is_dump_host_valid(host)
351 char *host;
352 {
353     struct stat dir_stat;
354     char *fn;
355     am_host_t *ihost;
356
357     if (config_name == NULL) {
358         reply(501, "Must set config before setting host.");
359         return -1;
360     }
361
362 #if 0
363     /* only let a client restore itself for now unless it is the server */
364     if (strcasecmp(remote_hostname, local_hostname) == 0)
365         return 0;
366     if (strcasecmp(remote_hostname, host) != 0)
367     {
368         reply(501,
369               "You don't have the necessary permissions to set dump host to %s.",
370               buf1);
371         return -1;
372     }
373 #endif
374
375     /* check that the config actually handles that host */
376     ihost = lookup_host(host);
377     if(ihost == NULL) {
378         reply(501, "Host %s is not in your disklist.", host);
379         return -1;
380     }
381
382     /* assume an index dir already */
383     fn = getindexfname(host, NULL, NULL, 0);
384     if (stat (fn, &dir_stat) != 0 || !S_ISDIR(dir_stat.st_mode)) {
385         reply(501, "No index records for host: %s. Have you enabled indexing?", host);
386         amfree(fn);
387         return -1;
388     }
389
390     amfree(fn);
391     return 0;
392 }
393
394
395 static int is_disk_valid(disk)
396 char *disk;
397 {
398     char *fn;
399     struct stat dir_stat;
400     disk_t *idisk;
401
402     if (config_name == NULL || dump_hostname == NULL) {
403         reply(501, "Must set config,host before setting disk.");
404         return -1;
405     }
406
407     /* check that the config actually handles that disk */
408     idisk = lookup_disk(dump_hostname, disk);
409     if(idisk == NULL) {
410         reply(501, "Disk %s:%s is not in your disklist.", dump_hostname, disk);
411         return -1;
412     }
413
414     /* assume an index dir already */
415     fn = getindexfname(dump_hostname, disk, NULL, 0);
416     if (stat (fn, &dir_stat) != 0 || !S_ISDIR(dir_stat.st_mode)) {
417         reply(501, "No index records for disk: %s. Invalid?", disk);
418         amfree(fn);
419         return -1;
420     }
421
422     amfree(fn);
423     return 0;
424 }
425
426
427 static int is_config_valid(config)
428 char *config;
429 {
430     char *conffile;
431     char *conf_diskfile;
432     char *conf_tapelist;
433     char *conf_indexdir;
434     struct stat dir_stat;
435
436     /* check that the config actually exists */
437     if (config == NULL) {
438         reply(501, "Must set config first.");
439         return -1;
440     }
441
442     /* read conffile */
443     conffile = stralloc2(config_dir, CONFFILE_NAME);
444     if (read_conffile(conffile)) {
445         reply(501, "Could not read config file %s!", conffile);
446         amfree(conffile);
447         return -1;
448     }
449     amfree(conffile);
450
451     conf_diskfile = getconf_str(CNF_DISKFILE);
452     if (*conf_diskfile == '/') {
453         conf_diskfile = stralloc(conf_diskfile);
454     } else {
455         conf_diskfile = stralloc2(config_dir, conf_diskfile);
456     }
457     if (read_diskfile(conf_diskfile, &disk_list) < 0) {
458         reply(501, "Could not read disk file %s!", conf_diskfile);
459         amfree(conf_diskfile);
460         return -1;
461     }
462     amfree(conf_diskfile);
463
464     conf_tapelist = getconf_str(CNF_TAPELIST);
465     if (*conf_tapelist == '/') {
466         conf_tapelist = stralloc(conf_tapelist);
467     } else {
468         conf_tapelist = stralloc2(config_dir, conf_tapelist);
469     }
470     if(read_tapelist(conf_tapelist)) {
471         reply(501, "Could not read tapelist file %s!", conf_tapelist);
472         amfree(conf_tapelist);
473         return -1;
474     }
475     amfree(conf_tapelist);
476
477     output_find = find_dump(1, &disk_list);
478     sort_find_result("DLKHpB", &output_find);
479
480     conf_indexdir = getconf_str(CNF_INDEXDIR);
481     if(*conf_indexdir == '/') {
482         conf_indexdir = stralloc(conf_indexdir);
483     } else {
484         conf_indexdir = stralloc2(config_dir, conf_indexdir);
485     }
486     if (stat (conf_indexdir, &dir_stat) != 0 || !S_ISDIR(dir_stat.st_mode)) {
487         reply(501, "Index directory %s does not exist", conf_indexdir);
488         amfree(conf_indexdir);
489         return -1;
490     }
491     amfree(conf_indexdir);
492
493     return 0;
494 }
495
496
497 static int build_disk_table()
498 {
499     char date[3 * NUM_STR_SIZE + 2 + 1];
500     long last_datestamp;
501     int last_filenum;
502     int last_level;
503     int last_partnum;
504     find_result_t *find_output;
505
506     if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
507         reply(590, "Must set config,host,disk before building disk table");
508         return -1;
509     }
510
511     clear_list();
512     last_datestamp = -1;
513     last_filenum = -1;
514     last_level = -1;
515     last_partnum = -1;
516     for(find_output = output_find;
517         find_output != NULL; 
518         find_output = find_output->next) {
519         if(strcasecmp(dump_hostname, find_output->hostname) == 0 &&
520            strcmp(disk_name    , find_output->diskname) == 0 &&
521            strcmp("OK"         , find_output->status)   == 0) {
522             int partnum = -1;
523             if(strcmp("--", find_output->partnum)){
524                 partnum = atoi(find_output->partnum);
525             }
526             /*
527              * The sort order puts holding disk entries first.  We want to
528              * use them if at all possible, so ignore any other entries
529              * for the same datestamp after we see a holding disk entry
530              * (as indicated by a filenum of zero).
531              */
532             if(find_output->datestamp == last_datestamp &&
533                find_output->level == last_level && 
534                partnum == last_partnum && last_filenum == 0) {
535                 continue;
536             }
537             last_datestamp = find_output->datestamp;
538             last_filenum = find_output->filenum;
539             last_level = find_output->level;
540             last_partnum = partnum;
541             snprintf(date, sizeof(date), "%04d-%02d-%02d",
542                         find_output->datestamp/10000,
543                         (find_output->datestamp/100) %100,
544                         find_output->datestamp %100);
545             add_dump(date, find_output->level, find_output->label, 
546                      find_output->filenum, partnum);
547             dbprintf(("%s: - %s %d %s %d %d\n",
548                      debug_prefix_time(NULL), date, find_output->level, 
549                      find_output->label, find_output->filenum, partnum));
550         }
551     }
552     return 0;
553 }
554
555
556 static int disk_history_list()
557 {
558     DUMP_ITEM *item;
559
560     if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
561         reply(502, "Must set config,host,disk before listing history");
562         return -1;
563     }
564
565     lreply(200, " Dump history for config \"%s\" host \"%s\" disk \"%s\"",
566           config_name, dump_hostname, disk_name);
567
568     for (item=first_dump(); item!=NULL; item=next_dump(item)){
569         char *tapelist_str = marshal_tapelist(item->tapes, 1);
570
571         if(am_has_feature(their_features, fe_amindexd_marshall_in_DHST)){
572             str_buffer_size = strlen(item->date) + NUM_STR_SIZE +
573                               strlen(tapelist_str) + 9;
574             lreply(201, " %s %d %s", item->date, item->level, tapelist_str);
575         }
576         else{
577             str_buffer_size = strlen(item->date) + NUM_STR_SIZE +
578                               strlen(tapelist_str) + NUM_STR_SIZE + 9;
579             lreply(201, " %s %d %s %d", item->date, item->level, tapelist_str,
580                item->file);
581         }
582         str_buffer_size = STR_SIZE;
583     }
584
585     reply(200, "Dump history for config \"%s\" host \"%s\" disk \"%s\"",
586           config_name, dump_hostname, disk_name);
587
588     return 0;
589 }
590
591
592 /* is the directory dir backed up - dir assumed complete relative to
593    disk mount point */
594 /* opaque version of command */
595 static int is_dir_valid_opaque(dir)
596 char *dir;
597 {
598     DUMP_ITEM *item;
599     char *line = NULL;
600     FILE *fp;
601     int last_level;
602     char *ldir = NULL;
603     char *filename_gz = NULL;
604     char *filename = NULL;
605     int ldir_len;
606     static char *emsg = NULL;
607
608     if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
609         reply(502, "Must set config,host,disk before asking about directories");
610         return -1;
611     }
612     if (target_date == NULL) {
613         reply(502, "Must set date before asking about directories");
614         return -1;
615     }
616
617     /* scan through till we find first dump on or before date */
618     for (item=first_dump(); item!=NULL; item=next_dump(item))
619         if (strcmp(item->date, target_date) <= 0)
620             break;
621
622     if (item == NULL)
623     {
624         /* no dump for given date */
625         reply(500, "No dumps available on or before date \"%s\"", target_date);
626         return -1;
627     }
628
629     if(strcmp(dir, "/") == 0) {
630         ldir = stralloc(dir);
631     } else {
632         ldir = stralloc2(dir, "/");
633     }
634     ldir_len = strlen(ldir);
635
636     /* go back till we hit a level 0 dump */
637     do
638     {
639         amfree(filename);
640         filename_gz = getindexfname(dump_hostname, disk_name,
641                                     item->date, item->level);
642         if((filename = uncompress_file(filename_gz, &emsg)) == NULL) {
643             reply(599, "System error %s", emsg);
644             amfree(filename_gz);
645             amfree(emsg);
646             amfree(ldir);
647             return -1;
648         }
649         amfree(filename_gz);
650         dbprintf(("%s: f %s\n", debug_prefix_time(NULL), filename));
651         if ((fp = fopen(filename, "r")) == NULL) {
652             reply(599, "System error %s", strerror(errno));
653             amfree(filename);
654             amfree(ldir);
655             return -1;
656         }
657         for(; (line = agets(fp)) != NULL; free(line)) {
658             if (strncmp(line, ldir, ldir_len) != 0) {
659                 continue;                       /* not found yet */
660             }
661             amfree(filename);
662             amfree(ldir);
663             amfree(line);
664             afclose(fp);
665             return 0;
666         }
667         afclose(fp);
668
669         last_level = item->level;
670         do
671         {
672             item=next_dump(item);
673         } while ((item != NULL) && (item->level >= last_level));
674     } while (item != NULL);
675
676     amfree(filename);
677     amfree(ldir);
678     reply(500, "\"%s\" is an invalid directory", dir);
679     return -1;
680 }
681
682 static int opaque_ls(dir,recursive)
683 char *dir;
684 int  recursive;
685 {
686     DUMP_ITEM *dump_item;
687     DIR_ITEM *dir_item;
688     int last_level;
689     static char *emsg = NULL;
690     am_feature_e marshall_feature;
691
692     if (recursive) {
693         marshall_feature = fe_amindexd_marshall_in_ORLD;
694     } else {
695         marshall_feature = fe_amindexd_marshall_in_OLSD;
696     }
697
698     clear_dir_list();
699
700     if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
701         reply(502, "Must set config,host,disk before listing a directory");
702         return -1;
703     }
704     if (target_date == NULL) {
705         reply(502, "Must set date before listing a directory");
706         return -1;
707     }
708
709     /* scan through till we find first dump on or before date */
710     for (dump_item=first_dump(); dump_item!=NULL; dump_item=next_dump(dump_item))
711         if (strcmp(dump_item->date, target_date) <= 0)
712             break;
713
714     if (dump_item == NULL)
715     {
716         /* no dump for given date */
717         reply(500, "No dumps available on or before date \"%s\"", target_date);
718         return -1;
719     }
720
721     /* get data from that dump */
722     if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
723         reply(599, "System error %s", emsg);
724         amfree(emsg);
725         return -1;
726     }
727
728     /* go back processing higher level dumps till we hit a level 0 dump */
729     last_level = dump_item->level;
730     while ((last_level != 0) && ((dump_item=next_dump(dump_item)) != NULL))
731     {
732         if (dump_item->level < last_level)
733         {
734             last_level = dump_item->level;
735             if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
736                 reply(599, "System error %s", emsg);
737                 amfree(emsg);
738                 return -1;
739             }
740         }
741     }
742
743     /* return the information to the caller */
744     lreply(200, " Opaque list of %s", dir);
745         for (dir_item = get_dir_list(); dir_item != NULL; 
746              dir_item = dir_item->next) {
747             char *tapelist_str;
748
749             if (!am_has_feature(their_features, marshall_feature) &&
750                 (num_entries(dir_item->dump->tapes) > 1 ||
751                 dir_item->dump->tapes->numfiles > 1)) {
752                 fast_lreply(501, " ERROR: Split dumps not supported"
753                             " with old version of amrecover.");
754                 break;
755             } else {
756                 if (am_has_feature(their_features, marshall_feature)) {
757                     tapelist_str = marshal_tapelist(dir_item->dump->tapes, 1);
758                 } else {
759                     tapelist_str = dir_item->dump->tapes->label;
760                 }
761                 
762                 if((!recursive && am_has_feature(their_features,
763                                                  fe_amindexd_fileno_in_OLSD))
764                    ||
765                    (recursive && am_has_feature(their_features,
766                                                 fe_amindexd_fileno_in_ORLD))) {
767                     str_buffer_size = strlen(dir_item->dump->date) +
768                         NUM_STR_SIZE + strlen(tapelist_str) + 
769                         strlen(dir_item->path) + NUM_STR_SIZE + 9;
770                     fast_lreply(201, " %s %d %s %d %s",
771                                 dir_item->dump->date, dir_item->dump->level,
772                                 tapelist_str, dir_item->dump->file,
773                                 dir_item->path);
774                 }
775                 else {
776                     str_buffer_size = strlen(dir_item->dump->date) +
777                         NUM_STR_SIZE + strlen(tapelist_str) +
778                         strlen(dir_item->path) + 9;
779                     fast_lreply(201, " %s %d %s %s",
780                                 dir_item->dump->date, dir_item->dump->level,
781                                 tapelist_str, dir_item->path);
782                 }
783                 if(am_has_feature(their_features, marshall_feature)) {
784                     amfree(tapelist_str);
785                 }
786                 str_buffer_size = STR_SIZE;
787             }
788     }
789     reply(200, " Opaque list of %s", dir);
790
791     clear_dir_list();
792     return 0;
793 }
794
795
796 /* returns the value of changer or tapedev from the amanda.conf file if set,
797    otherwise reports an error */
798 static int tapedev_is()
799 {
800     char *result;
801
802     /* check state okay to do this */
803     if (config_name == NULL) {
804         reply(501, "Must set config before asking about tapedev.");
805         return -1;
806     }
807
808     /* use amrecover_changer if possible */
809     if ((result = getconf_str(CNF_AMRECOVER_CHANGER)) != NULL  &&
810         *result != '\0') {
811         dbprintf(("%s: tapedev_is amrecover_changer: %s\n",
812                   debug_prefix_time(NULL), result));
813         reply(200, result);
814         return 0;
815     }
816
817     /* use changer if possible */
818     if ((result = getconf_str(CNF_TPCHANGER)) != NULL  &&  *result != '\0') {
819         dbprintf(("%s: tapedev_is tpchanger: %s\n",
820                   debug_prefix_time(NULL), result));
821         reply(200, result);
822         return 0;
823     }
824
825     /* get tapedev value */
826     if ((result = getconf_str(CNF_TAPEDEV)) != NULL  &&  *result != '\0') {
827         dbprintf(("%s: tapedev_is tapedev: %s\n",
828                   debug_prefix_time(NULL), result));
829         reply(200, result);
830         return 0;
831     }
832
833     dbprintf(("%s: No tapedev or changer in config site.\n",
834               debug_prefix_time(NULL)));
835     reply(501, "Tapedev or changer not set in config file.");
836     return -1;
837 }
838
839
840 /* returns YES if dumps for disk are compressed, NO if not */
841 static int are_dumps_compressed()
842 {
843     disk_t *diskp;
844
845     /* check state okay to do this */
846     if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
847         reply(501, "Must set config,host,disk name before asking about dumps.");
848         return -1;
849     }
850
851     /* now go through the list of disks and find which have indexes */
852     for (diskp = disk_list.head; diskp != NULL; diskp = diskp->next)
853         if ((strcasecmp(diskp->host->hostname, dump_hostname) == 0)
854             && (strcmp(diskp->name, disk_name) == 0))
855             break;
856
857     if (diskp == NULL)
858     {
859         reply(501, "Couldn't find host/disk in disk file.");
860         return -1;
861     }
862
863     /* send data to caller */
864     if (diskp->compress == COMP_NONE)
865         reply(200, "NO");
866     else
867         reply(200, "YES");
868
869     return 0;
870 }
871
872 int main(argc, argv)
873 int argc;
874 char **argv;
875 {
876     char *line = NULL, *part = NULL;
877     char *s, *fp;
878     int ch;
879     char *cmd_undo, cmd_undo_ch;
880     socklen_t socklen;
881     struct sockaddr_in his_addr;
882     struct hostent *his_name;
883     char *arg;
884     char *cmd;
885     int len;
886     int user_validated = 0;
887     char *errstr = NULL;
888     char *pgm = "amindexd";                     /* in case argv[0] is not set */
889
890     safe_fd(-1, 0);
891     safe_cd();
892
893     /*
894      * When called via inetd, it is not uncommon to forget to put the
895      * argv[0] value on the config line.  On some systems (e.g. Solaris)
896      * this causes argv and/or argv[0] to be NULL, so we have to be
897      * careful getting our name.
898      */
899     if (argc >= 1 && argv != NULL && argv[0] != NULL) {
900         if((pgm = strrchr(argv[0], '/')) != NULL) {
901             pgm++;
902         } else {
903             pgm = argv[0];
904         }
905     }
906
907     set_pname(pgm);
908
909     /* Don't die when child closes pipe */
910     signal(SIGPIPE, SIG_IGN);
911
912 #ifdef FORCE_USERID
913
914     /* we'd rather not run as root */
915
916     if(geteuid() == 0) {
917         if(client_uid == (uid_t) -1) {
918             error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
919         }
920
921         initgroups(CLIENT_LOGIN, client_gid);
922         setgid(client_gid);
923         setuid(client_uid);
924     }
925
926 #endif  /* FORCE_USERID */
927
928     dbopen();
929     dbprintf(("%s: version %s\n", get_pname(), version()));
930
931     if(argv == NULL) {
932         error("argv == NULL\n");
933     }
934
935     if (! (argc >= 1 && argv[0] != NULL)) {
936         dbprintf(("%s: WARNING: argv[0] not defined: check inetd.conf\n",
937                   debug_prefix_time(NULL)));
938     }
939
940     {
941         int db_fd = dbfd();
942         if(db_fd != -1) {
943             dup2(db_fd, 2);
944         }
945     }
946
947     /* initialize */
948
949     argc--;
950     argv++;
951
952     if(argc > 0 && strcmp(*argv, "-t") == 0) {
953         amindexd_debug = 1;
954         argc--;
955         argv++;
956     }
957
958     if (argc > 0) {
959         config_name = stralloc(*argv);
960         config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
961         argc--;
962         argv++;
963     }
964
965     if(gethostname(local_hostname, sizeof(local_hostname)-1) == -1)
966         error("gethostname: %s", strerror(errno));
967     local_hostname[sizeof(local_hostname)-1] = '\0';
968
969     /* now trim domain off name */
970     s = local_hostname;
971     ch = *s++;
972     while(ch && ch != '.') ch = *s++;
973     s[-1] = '\0';
974
975     if(amindexd_debug) {
976         /*
977          * Fake the remote address as the local address enough to get
978          * through the security check.
979          */
980         his_name = gethostbyname(local_hostname);
981         if(his_name == NULL) {
982             error("gethostbyname(%s) failed\n", local_hostname);
983         }
984         assert(his_name->h_addrtype == AF_INET);
985         his_addr.sin_family = his_name->h_addrtype;
986         his_addr.sin_port = htons(0);
987         memcpy((char *)&his_addr.sin_addr.s_addr,
988                (char *)his_name->h_addr_list[0], his_name->h_length);
989     } else {
990         /* who are we talking to? */
991         socklen = sizeof (his_addr);
992         if (getpeername(0, (struct sockaddr *)&his_addr, &socklen) == -1)
993             error("getpeername: %s", strerror(errno));
994     }
995     if (his_addr.sin_family != AF_INET || ntohs(his_addr.sin_port) == 20)
996     {
997         error("connection rejected from %s family %d port %d",
998               inet_ntoa(his_addr.sin_addr), his_addr.sin_family,
999               htons(his_addr.sin_port));
1000     }
1001     if ((his_name = gethostbyaddr((char *)&(his_addr.sin_addr),
1002                                   sizeof(his_addr.sin_addr),
1003                                   AF_INET)) == NULL) {
1004         error("gethostbyaddr(%s): hostname lookup failed",
1005               inet_ntoa(his_addr.sin_addr));
1006     }
1007     fp = s = his_name->h_name;
1008     ch = *s++;
1009     while(ch && ch != '.') ch = *s++;
1010     s[-1] = '\0';
1011     remote_hostname = newstralloc(remote_hostname, fp);
1012     s[-1] = ch;
1013
1014     /* clear these so we can detect when the have not been set by the client */
1015     amfree(dump_hostname);
1016     amfree(disk_name);
1017     amfree(target_date);
1018
1019     our_features = am_init_feature_set();
1020     their_features = am_set_default_feature_set();
1021
1022     if (config_name != NULL && is_config_valid(config_name) != -1) {
1023         return 1;
1024     }
1025
1026     reply(220, "%s AMANDA index server (%s) ready.", local_hostname,
1027           version());
1028
1029     /* a real simple parser since there are only a few commands */
1030     while (1)
1031     {
1032         /* get a line from the client */
1033         amfree(line);
1034         while(1) {
1035             if((part = agets(stdin)) == NULL) {
1036                 if(errno != 0) {
1037                     dbprintf(("%s: ? read error: %s\n",
1038                               debug_prefix_time(NULL), strerror(errno)));
1039                 } else {
1040                     dbprintf(("%s: ? unexpected EOF\n",
1041                               debug_prefix_time(NULL)));
1042                 }
1043                 if(line) {
1044                     dbprintf(("%s: ? unprocessed input:\n",
1045                               debug_prefix_time(NULL)));
1046                     dbprintf(("-----\n"));
1047                     dbprintf(("? %s\n", line));
1048                     dbprintf(("-----\n"));
1049                 }
1050                 amfree(line);
1051                 amfree(part);
1052                 uncompress_remove = remove_files(uncompress_remove);
1053                 dbclose();
1054                 return 1;               /* they hung up? */
1055             }
1056             if(line) {
1057                 strappend(line, part);
1058                 amfree(part);
1059             } else {
1060                 line = part;
1061                 part = NULL;
1062             }
1063             if(amindexd_debug) {
1064                 break;                  /* we have a whole line */
1065             }
1066             if((len = strlen(line)) > 0 && line[len-1] == '\r') {
1067                 line[len-1] = '\0';     /* zap the '\r' */
1068                 break;
1069             }
1070             /*
1071              * Hmmm.  We got a "line" from agets(), which means it saw
1072              * a '\n' (or EOF, etc), but there was not a '\r' before it.
1073              * Put a '\n' back in the buffer and loop for more.
1074              */
1075             strappend(line, "\n");
1076         }
1077
1078         dbprintf(("%s: > %s\n", debug_prefix_time(NULL), line));
1079
1080         arg = NULL;
1081         s = line;
1082         ch = *s++;
1083
1084         skip_whitespace(s, ch);
1085         if(ch == '\0') {
1086             reply(500, "Command not recognised/incorrect: %s", line);
1087             continue;
1088         }
1089         cmd = s - 1;
1090
1091         skip_non_whitespace(s, ch);
1092         cmd_undo = s-1;                         /* for error message */
1093         cmd_undo_ch = *cmd_undo;
1094         *cmd_undo = '\0';
1095         if (ch) {
1096             skip_whitespace(s, ch);             /* find the argument */
1097             if (ch) {
1098                 arg = s-1;
1099                 skip_non_whitespace(s, ch);
1100             }
1101         }
1102
1103         amfree(errstr);
1104         if (!user_validated && strcmp(cmd, "SECURITY") == 0 && arg) {
1105             user_validated = check_security(&his_addr, arg, 0, &errstr);
1106             if(user_validated) {
1107                 reply(200, "Access OK");
1108                 continue;
1109             }
1110         }
1111         if (!user_validated) {  /* don't tell client the reason, just log it to debug log */
1112             reply(500, "Access not allowed");
1113             if (errstr) {   
1114                 dbprintf(("%s: %s\n", debug_prefix_time(NULL), errstr));
1115             }
1116             break;
1117         }
1118
1119         if (strcmp(cmd, "QUIT") == 0) {
1120             break;
1121         } else if (strcmp(cmd, "HOST") == 0 && arg) {
1122             /* set host we are restoring */
1123             s[-1] = '\0';
1124             if (is_dump_host_valid(arg) != -1)
1125             {
1126                 dump_hostname = newstralloc(dump_hostname, arg);
1127                 reply(200, "Dump host set to %s.", dump_hostname);
1128                 amfree(disk_name);              /* invalidate any value */
1129             }
1130             s[-1] = ch;
1131         } else if (strcmp(cmd, "DISK") == 0 && arg) {
1132             s[-1] = '\0';
1133             if (is_disk_valid(arg) != -1) {
1134                 disk_name = newstralloc(disk_name, arg);
1135                 if (build_disk_table() != -1) {
1136                     reply(200, "Disk set to %s.", disk_name);
1137                 }
1138             }
1139             s[-1] = ch;
1140         } else if (strcmp(cmd, "LISTDISK") == 0) {
1141             disk_t *disk;
1142             int nbdisk = 0;
1143             s[-1] = '\0';
1144             if (config_name == NULL || dump_hostname == NULL) {
1145                 reply(501, "Must set config, host before listdisk");
1146             }
1147             else if(arg) {
1148                 lreply(200, " List of disk for device %s on host %s", arg,
1149                        dump_hostname);
1150                 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1151                     if(strcmp(disk->host->hostname, dump_hostname) == 0 &&
1152                        ((disk->device && strcmp(disk->device, arg) == 0) ||
1153                         (!disk->device && strcmp(disk->name, arg) == 0))) {
1154                         fast_lreply(201, " %s", disk->name);
1155                         nbdisk++;
1156                     }
1157                 }
1158                 if(nbdisk > 0) {
1159                     reply(200, "List of disk for device %s on host %s", arg,
1160                           dump_hostname);
1161                 }
1162                 else {
1163                     reply(200, "No disk for device %s on host %s", arg,
1164                           dump_hostname);
1165                 }
1166             }
1167             else {
1168                 lreply(200, " List of disk for host %s", dump_hostname);
1169                 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1170                     if(strcmp(disk->host->hostname, dump_hostname) == 0) {
1171                         fast_lreply(201, " %s", disk->name);
1172                         nbdisk++;
1173                     }
1174                 }
1175                 if(nbdisk > 0) {
1176                     reply(200, "List of disk for host %s", dump_hostname);
1177                 }
1178                 else {
1179                     reply(200, "No disk for host %s", dump_hostname);
1180                 }
1181             }
1182             s[-1] = ch;
1183         } else if (strcmp(cmd, "SCNF") == 0 && arg) {
1184             s[-1] = '\0';
1185             amfree(config_name);
1186             amfree(config_dir);
1187             config_name = newstralloc(config_name, arg);
1188             config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
1189             if (is_config_valid(arg) != -1) {
1190                 amfree(dump_hostname);          /* invalidate any value */
1191                 amfree(disk_name);              /* invalidate any value */
1192                 reply(200, "Config set to %s.", config_name);
1193             } else {
1194                 amfree(config_name);
1195                 amfree(config_dir);
1196             }
1197             s[-1] = ch;
1198         } else if (strcmp(cmd, "FEATURES") == 0 && arg) {
1199             char *our_feature_string = NULL;
1200             char *their_feature_string = NULL;
1201             s[-1] = '\0';
1202             am_release_feature_set(our_features);
1203             am_release_feature_set(their_features);
1204             our_features = am_init_feature_set();
1205             our_feature_string = am_feature_to_string(our_features);
1206             their_feature_string = newstralloc(target_date, arg);
1207             their_features = am_string_to_feature(their_feature_string);
1208             reply(200, "FEATURES %s", our_feature_string);
1209             amfree(our_feature_string);
1210             amfree(their_feature_string);
1211             s[-1] = ch;
1212         } else if (strcmp(cmd, "DATE") == 0 && arg) {
1213             s[-1] = '\0';
1214             target_date = newstralloc(target_date, arg);
1215             reply(200, "Working date set to %s.", target_date);
1216             s[-1] = ch;
1217         } else if (strcmp(cmd, "DHST") == 0) {
1218             (void)disk_history_list();
1219         } else if (strcmp(cmd, "OISD") == 0 && arg) {
1220             if (is_dir_valid_opaque(arg) != -1) {
1221                 reply(200, "\"%s\" is a valid directory", arg);
1222             }
1223         } else if (strcmp(cmd, "OLSD") == 0 && arg) {
1224             (void)opaque_ls(arg,0);
1225         } else if (strcmp(cmd, "ORLD") == 0 && arg) {
1226             (void)opaque_ls(arg,1);
1227         } else if (strcmp(cmd, "TAPE") == 0) {
1228             (void)tapedev_is();
1229         } else if (strcmp(cmd, "DCMP") == 0) {
1230             (void)are_dumps_compressed();
1231         } else {
1232             *cmd_undo = cmd_undo_ch;    /* restore the command line */
1233             reply(500, "Command not recognised/incorrect: %s", cmd);
1234         }
1235     }
1236     amfree(line);
1237
1238     uncompress_remove = remove_files(uncompress_remove);
1239     free_find_result(&output_find);
1240     reply(200, "Good bye.");
1241     dbclose();
1242     return 0;
1243 }