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