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