2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998 University of Maryland at College Park
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.
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.
23 * Authors: the Amanda Development Team. Its members are listed in a
24 * file named AUTHORS, in the root directory of this distribution.
27 * $Id: amindexd.c,v 1.106 2006/07/25 18:27:57 martinea Exp $
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
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
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.
50 #include "disk_history.h"
57 #include "pipespawn.h"
58 #include "sockaddr-util.h"
63 #define amindexd_debug(i,x) do { \
64 if ((i) <= debug_amindexd) { \
69 typedef struct REMOVE_ITEM
72 struct REMOVE_ITEM *next;
76 static int from_amandad;
77 static char local_hostname[MAX_HOSTNAME_LENGTH+1]; /* me! */
78 static char *dump_hostname = NULL; /* machine we are restoring */
79 static char *disk_name; /* disk we are restoring */
80 char *qdisk_name = NULL; /* disk we are restoring */
81 static char *target_date = NULL;
82 static disklist_t disk_list; /* all disks in cur config */
83 static find_result_t *output_find = NULL;
84 static g_option_t *g_options = NULL;
86 static int amindexd_debug = 0;
88 static REMOVE_ITEM *uncompress_remove = NULL;
89 /* uncompressed files to remove */
91 static am_feature_t *our_features = NULL;
92 static am_feature_t *their_features = NULL;
94 static REMOVE_ITEM *remove_files(REMOVE_ITEM *);
95 static char *uncompress_file(char *, char **);
96 static int process_ls_dump(char *, DUMP_ITEM *, int, char **);
98 static size_t reply_buffer_size = 1;
99 static char *reply_buffer = NULL;
100 static char *amandad_auth = NULL;
104 static void reply(int, char *, ...) G_GNUC_PRINTF(2, 3);
105 static void lreply(int, char *, ...) G_GNUC_PRINTF(2, 3);
106 static void fast_lreply(int, char *, ...) G_GNUC_PRINTF(2, 3);
107 static am_host_t *is_dump_host_valid(char *);
108 static int is_disk_valid(char *);
109 static int check_and_load_config(char *);
110 static int build_disk_table(void);
111 static int disk_history_list(void);
112 static int is_dir_valid_opaque(char *);
113 static int opaque_ls(char *, int);
114 static void opaque_ls_one (DIR_ITEM *dir_item, am_feature_e marshall_feature,
116 static int tapedev_is(void);
117 static int are_dumps_compressed(void);
118 static char *amindexd_nicedate (char *datestamp);
119 static int cmp_date (const char *date1, const char *date2);
120 static char *clean_backslash(char *line);
121 static char *get_index_name(char *dump_hostname, char *hostname,
122 char *diskname, char *timestamps, int level);
123 static int get_index_dir(char *dump_hostname, char *hostname, char *diskname);
125 int main(int, char **);
134 dbprintf(_("removing index file: %s\n"), remove->filename);
135 unlink(remove->filename);
136 amfree(remove->filename);
138 remove = remove->next;
150 char *filename = NULL;
151 struct stat stat_filename;
163 amwait_t wait_status;
166 filename = stralloc(filename_gz);
167 len = strlen(filename);
168 if(len > 3 && strcmp(&(filename[len-3]),".gz")==0) {
169 filename[len-3]='\0';
170 } else if(len > 2 && strcmp(&(filename[len-2]),".Z")==0) {
171 filename[len-2]='\0';
174 /* uncompress the file */
175 result=stat(filename,&stat_filename);
176 if(result==-1 && errno==ENOENT) { /* file does not exist */
178 REMOVE_ITEM *remove_file;
181 * Check that compressed file exists manually.
183 if (stat(filename_gz, &statbuf) < 0) {
184 *emsg = newvstrallocf(*emsg,
185 _("Compressed file '%s' is inaccessable: %s"),
186 filename_gz, strerror(errno));
187 dbprintf("%s\n",*emsg);
192 #ifdef UNCOMPRESS_OPT
193 # define PARAM_UNCOMPRESS_OPT UNCOMPRESS_OPT
195 # define PARAM_UNCOMPRESS_OPT skip_argument
198 nullfd = open("/dev/null", O_RDONLY);
200 indexfd = open(filename,O_WRONLY|O_CREAT, 0600);
202 *emsg = newvstrallocf(*emsg, _("Can't open '%s' for writting: %s"),
203 filename, strerror(errno));
204 dbprintf("%s\n",*emsg);
210 /* just use our stderr directly for the pipe's stderr; in
211 * main() we send stderr to the debug file, or /dev/null
212 * if debugging is disabled */
213 debugfd = STDERR_FILENO;
215 /* start the uncompress process */
216 putenv(stralloc("LC_ALL=C"));
217 pid_gzip = pipespawn(UNCOMPRESS_PATH, STDOUT_PIPE, 0,
218 &nullfd, &pipe_from_gzip, &debugfd,
219 UNCOMPRESS_PATH, PARAM_UNCOMPRESS_OPT,
223 pipe_stream = fdopen(pipe_from_gzip,"r");
224 if(pipe_stream == NULL) {
225 *emsg = newvstrallocf(*emsg, _("Can't fdopen pipe from gzip: %s"),
227 dbprintf("%s\n",*emsg);
233 /* start the sort process */
234 putenv(stralloc("LC_ALL=C"));
235 pid_sort = pipespawn(SORT_PATH, STDIN_PIPE, 0,
236 &pipe_to_sort, &indexfd, &debugfd,
240 /* send all ouput from uncompress process to sort process */
241 /* clean the data with clean_backslash */
242 while (fgets(line, STR_SIZE, pipe_stream) != NULL) {
243 if (line[0] != '\0') {
244 if (index(line,'/')) {
245 clean_backslash(line);
246 full_write(pipe_to_sort,line,strlen(line));
252 aclose(pipe_to_sort);
253 if (waitpid(pid_gzip, &wait_status, 0) < 0) {
254 if (!WIFEXITED(wait_status)) {
255 dbprintf(_("Uncompress exited with signal %d"),
256 WTERMSIG(wait_status));
257 } else if (WEXITSTATUS(wait_status) != 0) {
258 dbprintf(_("Uncompress exited with status %d"),
259 WEXITSTATUS(wait_status));
261 dbprintf(_("Uncompres returned negative value: %s"),
265 if (waitpid(pid_sort, &wait_status, 0) < 0) {
266 if (!WIFEXITED(wait_status)) {
267 dbprintf(_("Sort exited with signal %d"),
268 WTERMSIG(wait_status));
269 } else if (WEXITSTATUS(wait_status) != 0) {
270 dbprintf(_("Sort exited with status %d"),
271 WEXITSTATUS(wait_status));
273 dbprintf(_("Sort returned negative value: %s"),
278 /* add at beginning */
279 remove_file = (REMOVE_ITEM *)alloc(SIZEOF(REMOVE_ITEM));
280 remove_file->filename = stralloc(filename);
281 remove_file->next = uncompress_remove;
282 uncompress_remove = remove_file;
283 } else if(!S_ISREG((stat_filename.st_mode))) {
285 *emsg = vstrallocf(_("\"%s\" is not a regular file"), filename);
295 /* find all matching entries in a dump listing */
296 /* return -1 if error */
300 DUMP_ITEM * dump_item,
304 char line[STR_SIZE], old_line[STR_SIZE];
305 char *filename = NULL;
307 char *dir_slash = NULL;
311 size_t len_dir_slash;
314 if (strcmp(dir, "/") == 0) {
315 dir_slash = stralloc(dir);
317 dir_slash = stralloc2(dir, "/");
320 filename_gz = get_index_name(dump_hostname, dump_item->hostname, disk_name,
321 dump_item->date, dump_item->level);
322 if (filename_gz == NULL) {
323 *emsg = stralloc(_("index file not found"));
327 filename = uncompress_file(filename_gz, emsg);
328 if(filename == NULL) {
335 if((fp = fopen(filename,"r"))==0) {
337 *emsg = vstrallocf("%s", strerror(errno));
343 len_dir_slash=strlen(dir_slash);
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) {
351 s = line + len_dir_slash;
353 while(ch && ch != '/')
354 ch = *s++;/* find end of the file name */
360 if(strcmp(line, old_line) != 0) {
361 add_dir_list_item(dump_item, line);
362 strcpy(old_line, line);
373 /* send a 1 line reply to the client */
374 printf_arglist_function1(static void reply, int, n, char *, fmt)
380 reply_buffer = alloc(reply_buffer_size);
383 arglist_start(args, fmt);
384 len = g_vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
387 if (len > -1 && (size_t)len < reply_buffer_size-1)
390 reply_buffer_size *= 2;
391 amfree(reply_buffer);
392 reply_buffer = alloc(reply_buffer_size);
395 if (g_fprintf(cmdout,"%03d %s\r\n", n, reply_buffer) < 0)
397 dbprintf(_("! error %d (%s) in printf\n"), errno, strerror(errno));
398 uncompress_remove = remove_files(uncompress_remove);
401 if (fflush(cmdout) != 0)
403 dbprintf(_("! error %d (%s) in fflush\n"), errno, strerror(errno));
404 uncompress_remove = remove_files(uncompress_remove);
407 dbprintf(_("< %03d %s\n"), n, reply_buffer);
410 /* send one line of a multi-line response */
411 printf_arglist_function1(static void lreply, int, n, char *, fmt)
417 reply_buffer = alloc(reply_buffer_size);
420 arglist_start(args, fmt);
421 len = g_vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
424 if (len > -1 && (size_t)len < reply_buffer_size-1)
427 reply_buffer_size *= 2;
428 amfree(reply_buffer);
429 reply_buffer = alloc(reply_buffer_size);
432 if (g_fprintf(cmdout,"%03d-%s\r\n", n, reply_buffer) < 0)
434 dbprintf(_("! error %d (%s) in printf\n"),
435 errno, strerror(errno));
436 uncompress_remove = remove_files(uncompress_remove);
439 if (fflush(cmdout) != 0)
441 dbprintf(_("! error %d (%s) in fflush\n"),
442 errno, strerror(errno));
443 uncompress_remove = remove_files(uncompress_remove);
447 dbprintf("< %03d-%s\n", n, reply_buffer);
451 /* send one line of a multi-line response */
452 printf_arglist_function1(static void fast_lreply, int, n, char *, fmt)
458 reply_buffer = alloc(reply_buffer_size);
461 arglist_start(args, fmt);
462 len = g_vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
465 if (len > -1 && (size_t)len < reply_buffer_size-1)
468 reply_buffer_size *= 2;
469 amfree(reply_buffer);
470 reply_buffer = alloc(reply_buffer_size);
473 if (g_fprintf(cmdout,"%03d-%s\r\n", n, reply_buffer) < 0)
475 dbprintf(_("! error %d (%s) in printf\n"),
476 errno, strerror(errno));
477 uncompress_remove = remove_files(uncompress_remove);
481 dbprintf("< %03d-%s\n", n, reply_buffer);
484 /* see if hostname is valid */
485 /* valid is defined to be that there is an index directory for it */
486 /* also do a security check on the requested dump hostname */
487 /* to restrict access to index records if required */
488 /* return -1 if not okay */
496 if (get_config_name() == NULL) {
497 reply(501, _("Must set config before setting host."));
501 /* check that the config actually handles that host */
502 ihost = lookup_host(host);
504 reply(501, _("Host %s is not in your disklist."), host);
508 /* check if an index dir exist */
509 if(get_index_dir(host, ihost->hostname, NULL)) {
513 /* check if an index dir exist for at least one DLE */
514 for(diskp = ihost->disks; diskp != NULL; diskp = diskp->hostnext) {
515 if (get_index_dir(diskp->hostname, NULL, NULL)) {
520 reply(501, _("No index records for host: %s. Have you enabled indexing?"),
533 if (get_config_name() == NULL) {
534 reply(501, _("Must set config,host before setting disk."));
537 else if (dump_hostname == NULL) {
538 reply(501, _("Must set host before setting disk."));
542 /* check that the config actually handles that disk */
543 idisk = lookup_disk(dump_hostname, disk);
545 qdisk = quote_string(disk);
546 reply(501, _("Disk %s:%s is not in your disklist."), dump_hostname, qdisk);
551 /* assume an index dir already */
552 if (get_index_dir(dump_hostname, idisk->hostname, disk) == 0) {
553 qdisk = quote_string(disk);
554 reply(501, _("No index records for disk: %s. Invalid?"), qdisk);
564 check_and_load_config(
570 struct stat dir_stat;
572 /* check that the config actually exists */
573 if (config == NULL) {
574 reply(501, _("Must set config first."));
578 /* (re-)initialize configuration with the new config name */
579 config_init(CONFIG_INIT_EXPLICIT_NAME, config);
580 if (config_errors(NULL) >= CFGERR_ERRORS) {
581 reply(501, _("Could not read config file for %s!"), config);
585 check_running_as(RUNNING_AS_DUMPUSER_PREFERRED);
587 conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
588 read_diskfile(conf_diskfile, &disk_list);
589 amfree(conf_diskfile);
590 if (config_errors(NULL) >= CFGERR_ERRORS) {
591 reply(501, _("Could not read disk file %s!"), conf_diskfile);
595 conf_tapelist = config_dir_relative(getconf_str(CNF_TAPELIST));
596 if(read_tapelist(conf_tapelist)) {
597 reply(501, _("Could not read tapelist file %s!"), conf_tapelist);
598 amfree(conf_tapelist);
601 amfree(conf_tapelist);
603 dbrename(get_config_name(), DBG_SUBDIR_SERVER);
605 output_find = find_dump(&disk_list);
606 /* the 'w' here sorts by write timestamp, so that the first instance of
607 * any particular datestamp/host/disk/level/part that we see is the one
609 sort_find_result("DLKHpwB", &output_find);
611 conf_indexdir = config_dir_relative(getconf_str(CNF_INDEXDIR));
612 if (stat (conf_indexdir, &dir_stat) != 0 || !S_ISDIR(dir_stat.st_mode)) {
613 reply(501, _("Index directory %s does not exist"), conf_indexdir);
614 amfree(conf_indexdir);
617 amfree(conf_indexdir);
624 build_disk_table(void)
627 char *last_timestamp;
631 find_result_t *find_output;
633 if (get_config_name() == NULL) {
634 reply(590, _("Must set config,host,disk before building disk table"));
637 else if (dump_hostname == NULL) {
638 reply(590, _("Must set host,disk before building disk table"));
641 else if (disk_name == NULL) {
642 reply(590, _("Must set disk before building disk table"));
647 last_timestamp = NULL;
648 last_filenum = (off_t)-1;
651 for(find_output = output_find;
653 find_output = find_output->next) {
654 if(strcasecmp(dump_hostname, find_output->hostname) == 0 &&
655 strcmp(disk_name , find_output->diskname) == 0 &&
656 strcmp("OK" , find_output->status) == 0) {
659 if (strcmp("1/1", find_output->partnum) == 0) {
661 } else if (strcmp("1/-1", find_output->partnum) == 0) {
662 if (find_output->next &&
663 strcmp(dump_hostname, find_output->next->hostname) == 0 &&
664 strcmp(disk_name, find_output->next->diskname) == 0 &&
665 strcmp(find_output->timestamp,
666 find_output->next->timestamp) == 0 &&
667 strcmp("OK", find_output->next->status) == 0 &&
668 strcmp("2/-1", find_output->next->partnum) == 0) {
674 } else if (strcmp("--", find_output->partnum)) {
676 partnum = atoi(find_output->partnum);
677 c = strchr(find_output->partnum,'/');
684 * The sort order puts holding disk entries first. We want to
685 * use them if at all possible, so ignore any other entries
686 * for the same datestamp after we see a holding disk entry
687 * (as indicated by a filenum of zero).
690 strcmp(find_output->timestamp, last_timestamp) == 0 &&
691 find_output->level == last_level &&
692 partnum == last_partnum && last_filenum == 0) {
695 /* ignore duplicate partnum */
697 strcmp(find_output->timestamp, last_timestamp) == 0 &&
698 find_output->level == last_level &&
699 partnum == last_partnum) {
702 last_timestamp = find_output->timestamp;
703 last_filenum = find_output->filenum;
704 last_level = find_output->level;
705 last_partnum = partnum;
706 date = amindexd_nicedate(find_output->timestamp);
707 add_dump(find_output->hostname, date, find_output->level,
708 find_output->label, find_output->filenum, partnum,
710 dbprintf("- %s %d %s %lld %d %d\n",
711 date, find_output->level,
713 (long long)find_output->filenum,
725 disk_history_list(void)
730 if (get_config_name() == NULL) {
731 reply(502, _("Must set config,host,disk before listing history"));
734 else if (dump_hostname == NULL) {
735 reply(502, _("Must set host,disk before listing history"));
738 else if (disk_name == NULL) {
739 reply(502, _("Must set disk before listing history"));
743 lreply(200, _(" Dump history for config \"%s\" host \"%s\" disk %s"),
744 get_config_name(), dump_hostname, qdisk_name);
746 for (item=first_dump(); item!=NULL; item=next_dump(item)){
747 char *tapelist_str = marshal_tapelist(item->tapes, 1);
749 strncpy(date, item->date, 20);
751 if(!am_has_feature(their_features,fe_amrecover_timestamp))
754 if(am_has_feature(their_features, fe_amindexd_marshall_in_DHST)){
755 lreply(201, " %s %d %s", date, item->level, tapelist_str);
758 lreply(201, " %s %d %s %lld", date, item->level,
759 tapelist_str, (long long)item->file);
761 amfree(tapelist_str);
764 reply(200, _("Dump history for config \"%s\" host \"%s\" disk %s"),
765 get_config_name(), dump_hostname, qdisk_name);
772 * is the directory dir backed up - dir assumed complete relative to
775 /* opaque version of command */
785 char *filename_gz = NULL;
786 char *filename = NULL;
788 static char *emsg = NULL;
790 if (get_config_name() == NULL || dump_hostname == NULL || disk_name == NULL) {
791 reply(502, _("Must set config,host,disk before asking about directories"));
794 else if (dump_hostname == NULL) {
795 reply(502, _("Must set host,disk before asking about directories"));
798 else if (disk_name == NULL) {
799 reply(502, _("Must set disk before asking about directories"));
802 else if (target_date == NULL) {
803 reply(502, _("Must set date before asking about directories"));
806 /* scan through till we find first dump on or before date */
807 for (item=first_dump(); item!=NULL; item=next_dump(item))
808 if (cmp_date(item->date, target_date) <= 0)
813 /* no dump for given date */
814 reply(500, _("No dumps available on or before date \"%s\""), target_date);
818 if(strcmp(dir, "/") == 0) {
819 ldir = stralloc(dir);
821 ldir = stralloc2(dir, "/");
823 ldir_len = strlen(ldir);
825 /* go back till we hit a level 0 dump */
829 filename_gz = get_index_name(dump_hostname, item->hostname, disk_name,
830 item->date, item->level);
831 if (filename_gz == NULL) {
832 reply(599, "index not found");
836 if((filename = uncompress_file(filename_gz, &emsg)) == NULL) {
837 reply(599, _("System error %s"), emsg);
844 dbprintf("f %s\n", filename);
845 if ((fp = fopen(filename, "r")) == NULL) {
846 reply(599, _("System error %s"), strerror(errno));
851 while (fgets(line, STR_SIZE, fp) != NULL) {
854 if(line[strlen(line)-1] == '\n')
855 line[strlen(line)-1] = '\0';
856 if (strncmp(line, ldir, ldir_len) != 0) {
857 continue; /* not found yet */
866 last_level = item->level;
869 item=next_dump(item);
870 } while ((item != NULL) && (item->level >= last_level));
871 } while (item != NULL);
875 reply(500, _("\"%s\" is an invalid directory"), dir);
884 DUMP_ITEM *dump_item;
886 int level, last_level;
887 static char *emsg = NULL;
888 am_feature_e marshall_feature;
891 marshall_feature = fe_amindexd_marshall_in_ORLD;
893 marshall_feature = fe_amindexd_marshall_in_OLSD;
898 if (get_config_name() == NULL) {
899 reply(502, _("Must set config,host,disk before listing a directory"));
902 else if (dump_hostname == NULL) {
903 reply(502, _("Must set host,disk before listing a directory"));
906 else if (disk_name == NULL) {
907 reply(502, _("Must set disk before listing a directory"));
910 else if (target_date == NULL) {
911 reply(502, _("Must set date before listing a directory"));
915 /* scan through till we find first dump on or before date */
916 for (dump_item=first_dump(); dump_item!=NULL; dump_item=next_dump(dump_item))
917 if (cmp_date(dump_item->date, target_date) <= 0)
920 if (dump_item == NULL)
922 /* no dump for given date */
923 reply(500, _("No dumps available on or before date \"%s\""), target_date);
927 /* get data from that dump */
928 if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
929 reply(599, _("System error %s"), emsg);
934 /* go back processing higher level dumps till we hit a level 0 dump */
935 last_level = dump_item->level;
936 while ((last_level != 0) && ((dump_item=next_dump(dump_item)) != NULL))
938 if (dump_item->level < last_level)
940 last_level = dump_item->level;
941 if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
942 reply(599, _("System error %s"), emsg);
949 /* return the information to the caller */
950 lreply(200, _(" Opaque list of %s"), dir);
951 for(level=0; level<=9; level++) {
952 for (dir_item = get_dir_list(); dir_item != NULL;
953 dir_item = dir_item->next) {
955 if(dir_item->dump->level == level) {
956 if (!am_has_feature(their_features, marshall_feature) &&
957 (num_entries(dir_item->dump->tapes) > 1 ||
958 dir_item->dump->tapes->numfiles > 1)) {
959 fast_lreply(501, _(" ERROR: Split dumps not supported"
960 " with old version of amrecover."));
964 opaque_ls_one(dir_item, marshall_feature, recursive);
969 reply(200, _(" Opaque list of %s"), dir);
977 am_feature_e marshall_feature,
984 if (am_has_feature(their_features, marshall_feature)) {
985 tapelist_str = marshal_tapelist(dir_item->dump->tapes, 1);
987 tapelist_str = dir_item->dump->tapes->label;
990 strncpy(date, dir_item->dump->date, 20);
992 if(!am_has_feature(their_features,fe_amrecover_timestamp))
995 qpath = quote_string(dir_item->path);
996 if((!recursive && am_has_feature(their_features,
997 fe_amindexd_fileno_in_OLSD)) ||
998 (recursive && am_has_feature(their_features,
999 fe_amindexd_fileno_in_ORLD))) {
1000 fast_lreply(201, " %s %d %s %lld %s",
1002 dir_item->dump->level,
1004 (long long)dir_item->dump->file,
1009 fast_lreply(201, " %s %d %s %s",
1010 date, dir_item->dump->level,
1011 tapelist_str, qpath);
1014 if(am_has_feature(their_features, marshall_feature)) {
1015 amfree(tapelist_str);
1020 * returns the value of changer or tapedev from the amanda.conf file if set,
1021 * otherwise reports an error
1029 /* check state okay to do this */
1030 if (get_config_name() == NULL) {
1031 reply(501, _("Must set config before asking about tapedev."));
1035 /* use amrecover_changer if possible */
1036 if ((result = getconf_str(CNF_AMRECOVER_CHANGER)) != NULL &&
1038 dbprintf(_("tapedev_is amrecover_changer: %s\n"), result);
1039 reply(200, "%s", result);
1043 /* use changer if possible */
1044 if ((result = getconf_str(CNF_TPCHANGER)) != NULL && *result != '\0') {
1045 dbprintf(_("tapedev_is tpchanger: %s\n"), result);
1046 reply(200, "%s", result);
1050 /* get tapedev value */
1051 if ((result = getconf_str(CNF_TAPEDEV)) != NULL && *result != '\0') {
1052 dbprintf(_("tapedev_is tapedev: %s\n"), result);
1053 reply(200, "%s", result);
1057 dbprintf(_("No tapedev or tpchanger in config site.\n"));
1059 reply(501, _("Tapedev or tpchanger not set in config file."));
1064 /* returns YES if dumps for disk are compressed, NO if not */
1066 are_dumps_compressed(void)
1070 /* check state okay to do this */
1071 if (get_config_name() == NULL) {
1072 reply(501, _("Must set config,host,disk name before asking about dumps."));
1075 else if (dump_hostname == NULL) {
1076 reply(501, _("Must set host,disk name before asking about dumps."));
1079 else if (disk_name == NULL) {
1080 reply(501, _("Must set disk name before asking about dumps."));
1084 /* now go through the list of disks and find which have indexes */
1085 for (diskp = disk_list.head; diskp != NULL; diskp = diskp->next) {
1086 if ((strcasecmp(diskp->host->hostname, dump_hostname) == 0)
1087 && (strcmp(diskp->name, disk_name) == 0)) {
1092 if (diskp == NULL) {
1093 reply(501, _("Couldn't find host/disk in disk file."));
1097 /* send data to caller */
1098 if (diskp->compress == COMP_NONE)
1111 char *line = NULL, *part = NULL;
1114 char *cmd_undo, cmd_undo_ch;
1115 socklen_t_equiv socklen;
1116 sockaddr_union his_addr;
1120 int user_validated = 0;
1121 char *errstr = NULL;
1122 char *pgm = "amindexd"; /* in case argv[0] is not set */
1123 char his_hostname[MAX_HOSTNAME_LENGTH];
1124 char *cfg_opt = NULL;
1127 * Configure program for internationalization:
1128 * 1) Only set the message locale for now.
1129 * 2) Set textdomain for all amanda related programs to "amanda"
1130 * We don't want to be forced to support dozens of message catalogs.
1132 setlocale(LC_MESSAGES, "C");
1133 textdomain("amanda");
1135 safe_fd(DATA_FD_OFFSET, 2);
1139 * When called via inetd, it is not uncommon to forget to put the
1140 * argv[0] value on the config line. On some systems (e.g. Solaris)
1141 * this causes argv and/or argv[0] to be NULL, so we have to be
1142 * careful getting our name.
1144 if (argc >= 1 && argv != NULL && argv[0] != NULL) {
1145 if((pgm = strrchr(argv[0], '/')) != NULL) {
1154 /* Don't die when child closes pipe */
1155 signal(SIGPIPE, SIG_IGN);
1157 dbopen(DBG_SUBDIR_SERVER);
1158 dbprintf(_("version %s\n"), version());
1161 error("argv == NULL\n");
1164 if (! (argc >= 1 && argv[0] != NULL)) {
1165 dbprintf(_("WARNING: argv[0] not defined: check inetd.conf\n"));
1168 debug_dup_stderr_to_debug();
1175 if(argc > 0 && strcmp(*argv, "-t") == 0) {
1181 if(argc > 0 && strcmp(*argv, "amandad") == 0) {
1186 amandad_auth = *argv;
1202 if(gethostname(local_hostname, SIZEOF(local_hostname)-1) == -1) {
1203 error(_("gethostname: %s"), strerror(errno));
1206 local_hostname[SIZEOF(local_hostname)-1] = '\0';
1208 /* now trim domain off name */
1211 while(ch && ch != '.') ch = *s++;
1215 if(from_amandad == 0) {
1216 if(!amindexd_debug) {
1217 /* who are we talking to? */
1218 socklen = sizeof (his_addr);
1219 if (getpeername(0, (struct sockaddr *)&his_addr, &socklen) == -1)
1220 error(_("getpeername: %s"), strerror(errno));
1222 /* Try a reverse (IP->hostname) resolution, and fail if it does
1223 * not work -- this is a basic security check */
1224 if (getnameinfo((struct sockaddr *)&his_addr, SS_LEN(&his_addr),
1225 his_hostname, sizeof(his_hostname),
1228 error(_("getnameinfo(%s): hostname lookup failed"),
1229 str_sockaddr(&his_addr));
1234 /* Set up the input and output FILEs */
1239 /* read the REQ packet */
1240 for(; (line = agets(stdin)) != NULL; free(line)) {
1241 if(strncmp_const(line, "OPTIONS ") == 0) {
1242 if (g_options != NULL) {
1243 dbprintf(_("REQ packet specified multiple OPTIONS.\n"));
1244 free_g_options(g_options);
1246 g_options = parse_g_options(line+8, 1);
1247 if(!g_options->hostname) {
1248 g_options->hostname = alloc(MAX_HOSTNAME_LENGTH+1);
1249 gethostname(g_options->hostname, MAX_HOSTNAME_LENGTH);
1250 g_options->hostname[MAX_HOSTNAME_LENGTH] = '\0';
1256 if(amandad_auth && g_options->auth) {
1257 if(strcasecmp(amandad_auth, g_options->auth) != 0) {
1258 g_printf(_("ERROR recover program ask for auth=%s while amindexd is configured for '%s'\n"),
1259 g_options->auth, amandad_auth);
1260 error(_("amindexd: ERROR recover program ask for auth=%s while amindexd is configured for '%s'"),
1261 g_options->auth, amandad_auth);
1265 /* send the REP packet */
1266 g_printf("CONNECT MESG %d\n", DATA_FD_OFFSET);
1273 cmdout = fdopen(DATA_FD_OFFSET + 0, "a");
1275 error(_("amindexd: Can't fdopen(%d): %s"), DATA_FD_OFFSET + 0, strerror(errno));
1279 cmdin = fdopen(DATA_FD_OFFSET + 1, "r");
1281 error(_("amindexd: Can't fdopen(%d): %s"), DATA_FD_OFFSET + 1, strerror(errno));
1286 /* clear these so we can detect when the have not been set by the client */
1287 amfree(dump_hostname);
1290 amfree(target_date);
1292 our_features = am_init_feature_set();
1293 their_features = am_set_default_feature_set();
1295 if (cfg_opt != NULL && check_and_load_config(cfg_opt) != -1) { /* load the config */
1299 reply(220, _("%s AMANDA index server (%s) ready."), local_hostname,
1302 user_validated = from_amandad;
1304 /* a real simple parser since there are only a few commands */
1307 /* get a line from the client */
1309 if((part = agets(cmdin)) == NULL) {
1311 dbprintf(_("? read error: %s\n"), strerror(errno));
1313 dbprintf(_("? unexpected EOF\n"));
1316 dbprintf(_("? unprocessed input:\n"));
1317 dbprintf("-----\n");
1318 dbprintf("? %s\n", line);
1319 dbprintf("-----\n");
1323 uncompress_remove = remove_files(uncompress_remove);
1325 return 1; /* they hung up? */
1327 strappend(line, part); /* Macro: line can be null */
1330 if(amindexd_debug) {
1331 break; /* we have a whole line */
1333 if((len = strlen(line)) > 0 && line[len-1] == '\r') {
1334 line[len-1] = '\0'; /* zap the '\r' */
1338 * Hmmm. We got a "line" from agets(), which means it saw
1339 * a '\n' (or EOF, etc), but there was not a '\r' before it.
1340 * Put a '\n' back in the buffer and loop for more.
1342 strappend(line, "\n");
1345 dbprintf("> %s\n", line);
1352 skip_whitespace(s, ch);
1354 reply(500, _("Command not recognised/incorrect: %s"), line);
1360 skip_non_whitespace(s, ch);
1361 cmd_undo = s-1; /* for error message */
1362 cmd_undo_ch = *cmd_undo;
1365 skip_whitespace(s, ch); /* find the argument */
1368 skip_quoted_string(s, ch);
1369 arg = unquote_string(arg);
1374 if (!user_validated && strcmp(cmd, "SECURITY") == 0 && arg) {
1375 user_validated = amindexd_debug ||
1377 (sockaddr_union *)&his_addr,
1379 if(user_validated) {
1380 reply(200, _("Access OK"));
1385 if (!user_validated) { /* don't tell client the reason, just log it to debug log */
1386 reply(500, _("Access not allowed"));
1388 dbprintf("%s\n", errstr);
1393 if (strcmp(cmd, "QUIT") == 0) {
1396 } else if (strcmp(cmd, "HOST") == 0 && arg) {
1398 /* set host we are restoring */
1400 if ((lhost = is_dump_host_valid(arg)) != NULL)
1402 dump_hostname = newstralloc(dump_hostname, lhost->hostname);
1403 reply(200, _("Dump host set to %s."), dump_hostname);
1404 amfree(qdisk_name); /* invalidate any value */
1405 amfree(disk_name); /* invalidate any value */
1408 } else if (strcmp(cmd, "LISTHOST") == 0) {
1414 if (get_config_name() == NULL) {
1415 reply(501, _("Must set config before listhost"));
1418 lreply(200, _(" List hosts for config %s"), get_config_name());
1419 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1421 for (diskdup = disk_list.head; diskdup!=disk; diskdup = diskdup->next) {
1422 if(strcmp(diskdup->host->hostname, disk->host->hostname) == 0) {
1428 fast_lreply(201, " %s", disk->host->hostname);
1433 reply(200, _(" List hosts for config %s"), get_config_name());
1436 reply(200, _("No hosts for config %s"), get_config_name());
1440 } else if (strcmp(cmd, "DISK") == 0 && arg) {
1442 if (is_disk_valid(arg) != -1) {
1443 disk_name = newstralloc(disk_name, arg);
1444 qdisk_name = quote_string(disk_name);
1445 if (build_disk_table() != -1) {
1446 reply(200, _("Disk set to %s."), qdisk_name);
1450 } else if (strcmp(cmd, "DLE") == 0) {
1456 dp = lookup_disk(dump_hostname, disk_name);
1457 if (dp->line == 0) {
1458 reply(200, "NODLE");
1460 b64disk = amxml_format_tag("disk", dp->name);
1461 optionstr = xml_optionstr(dp, their_features, NULL, 0);
1462 l = vstralloc("<dle>\n",
1463 " <program>", dp->program, "</program>\n", NULL);
1464 if (dp->application) {
1465 char *xml_app = xml_application(dp->application,
1467 vstrextend(&l, xml_app, NULL);
1470 vstrextend(&l, " ", b64disk, "\n", NULL);
1472 char *b64device = amxml_format_tag("diskdevice", dp->device);
1473 vstrextend(&l, " ", b64device, "\n", NULL);
1476 vstrextend(&l, optionstr, "</dle>\n", NULL);
1477 ql = quote_string(l);
1478 reply(200, "%s", ql);
1484 } else if (strcmp(cmd, "LISTDISK") == 0) {
1489 if (get_config_name() == NULL) {
1490 reply(501, _("Must set config, host before listdisk"));
1492 else if (dump_hostname == NULL) {
1493 reply(501, _("Must set host before listdisk"));
1496 lreply(200, _(" List of disk for device %s on host %s"), arg,
1498 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1500 if (strcasecmp(disk->host->hostname, dump_hostname) == 0 &&
1501 ((disk->device && strcmp(disk->device, arg) == 0) ||
1502 (!disk->device && strcmp(disk->name, arg) == 0))) {
1503 qname = quote_string(disk->name);
1504 fast_lreply(201, " %s", qname);
1510 reply(200, _("List of disk for device %s on host %s"), arg,
1514 reply(200, _("No disk for device %s on host %s"), arg,
1519 lreply(200, _(" List of disk for host %s"), dump_hostname);
1520 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1521 if(strcasecmp(disk->host->hostname, dump_hostname) == 0) {
1522 qname = quote_string(disk->name);
1523 fast_lreply(201, " %s", qname);
1529 reply(200, _("List of disk for host %s"), dump_hostname);
1532 reply(200, _("No disk for host %s"), dump_hostname);
1536 } else if (strcmp(cmd, "SCNF") == 0 && arg) {
1538 if (check_and_load_config(arg) != -1) { /* try to load the new config */
1539 amfree(dump_hostname); /* invalidate any value */
1540 amfree(qdisk_name); /* invalidate any value */
1541 amfree(disk_name); /* invalidate any value */
1542 reply(200, _("Config set to %s."), get_config_name());
1543 } /* check_and_load_config replies with any failure messages */
1545 } else if (strcmp(cmd, "FEATURES") == 0 && arg) {
1546 char *our_feature_string = NULL;
1547 char *their_feature_string = NULL;
1549 am_release_feature_set(our_features);
1550 am_release_feature_set(their_features);
1551 our_features = am_init_feature_set();
1552 our_feature_string = am_feature_to_string(our_features);
1553 their_feature_string = newstralloc(their_feature_string, arg);
1554 their_features = am_string_to_feature(their_feature_string);
1555 reply(200, "FEATURES %s", our_feature_string);
1556 amfree(our_feature_string);
1557 amfree(their_feature_string);
1559 } else if (strcmp(cmd, "DATE") == 0 && arg) {
1561 target_date = newstralloc(target_date, arg);
1562 reply(200, _("Working date set to %s."), target_date);
1564 } else if (strcmp(cmd, "DHST") == 0) {
1565 (void)disk_history_list();
1566 } else if (strcmp(cmd, "OISD") == 0 && arg) {
1567 if (is_dir_valid_opaque(arg) != -1) {
1568 reply(200, _("\"%s\" is a valid directory"), arg);
1570 } else if (strcmp(cmd, "OLSD") == 0 && arg) {
1571 (void)opaque_ls(arg,0);
1572 } else if (strcmp(cmd, "ORLD") == 0 && arg) {
1573 (void)opaque_ls(arg, 1);
1574 } else if (strcmp(cmd, "TAPE") == 0) {
1576 } else if (strcmp(cmd, "DCMP") == 0) {
1577 (void)are_dumps_compressed();
1579 *cmd_undo = cmd_undo_ch; /* restore the command line */
1580 reply(500, _("Command not recognised/incorrect: %s"), cmd);
1586 uncompress_remove = remove_files(uncompress_remove);
1587 free_find_result(&output_find);
1588 reply(200, _("Good bye."));
1597 static char nice[20];
1598 int year, month, day;
1599 int hours, minutes, seconds;
1600 char date[9], atime[7];
1601 int numdate, numtime;
1603 strncpy(date, datestamp, 8);
1605 numdate = atoi(date);
1606 year = numdate / 10000;
1607 month = (numdate / 100) % 100;
1608 day = numdate % 100;
1610 if(strlen(datestamp) <= 8) {
1611 g_snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d",
1615 strncpy(atime, &(datestamp[8]), 6);
1617 numtime = atoi(atime);
1618 hours = numtime / 10000;
1619 minutes = (numtime / 100) % 100;
1620 seconds = numtime % 100;
1622 g_snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d-%02d-%02d-%02d",
1623 year, month, day, hours, minutes, seconds);
1634 return strncmp(date1, date2, strlen(date2));
1641 char *s = line, *s1, *s2;
1650 if (*s != '\0' && isdigit((int)*s) &&
1651 *s1 != '\0' && isdigit((int)*s1) &&
1652 *s2 != '\0' && isdigit((int)*s2)) {
1653 /* this is \000, an octal value */
1654 i = ((*s)-'0')*64 + ((*s1)-'0')*8 + ((*s2)-'0');
1657 } else if (*s == '\\') { /* we remove one / */
1659 } else { /* we keep the / */
1675 char *dump_hostname,
1679 struct stat dir_stat;
1682 char *lower_hostname;
1684 lower_hostname = stralloc(dump_hostname);
1685 for(s=lower_hostname; *s != '\0'; s++)
1688 fn = getindexfname(dump_hostname, diskname, NULL, 0);
1689 if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1690 amfree(lower_hostname);
1695 if (hostname != NULL) {
1696 fn = getindexfname(hostname, diskname, NULL, 0);
1697 if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1698 amfree(lower_hostname);
1704 fn = getindexfname(lower_hostname, diskname, NULL, 0);
1705 if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1706 amfree(lower_hostname);
1711 if(diskname != NULL) {
1712 fn = getoldindexfname(dump_hostname, diskname, NULL, 0);
1713 if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1714 amfree(lower_hostname);
1719 if (hostname != NULL) {
1720 fn = getoldindexfname(hostname, diskname, NULL, 0);
1721 if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1722 amfree(lower_hostname);
1728 fn = getoldindexfname(lower_hostname, diskname, NULL, 0);
1729 if (stat(fn, &dir_stat) == 0 && S_ISDIR(dir_stat.st_mode)) {
1730 amfree(lower_hostname);
1736 amfree(lower_hostname);
1742 char *dump_hostname,
1748 struct stat dir_stat;
1751 char *lower_hostname;
1753 lower_hostname = stralloc(dump_hostname);
1754 for(s=lower_hostname; *s != '\0'; s++)
1757 fn = getindexfname(dump_hostname, diskname, timestamps, level);
1758 if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1759 amfree(lower_hostname);
1763 if(hostname != NULL) {
1764 fn = getindexfname(hostname, diskname, timestamps, level);
1765 if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1766 amfree(lower_hostname);
1771 fn = getindexfname(lower_hostname, diskname, timestamps, level);
1772 if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1773 amfree(lower_hostname);
1777 if(diskname != NULL) {
1778 fn = getoldindexfname(dump_hostname, diskname, timestamps, level);
1779 if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1780 amfree(lower_hostname);
1784 if(hostname != NULL) {
1785 fn = getoldindexfname(hostname, diskname, timestamps, level);
1786 if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1787 amfree(lower_hostname);
1792 fn = getoldindexfname(lower_hostname, diskname, timestamps, level);
1793 if (stat(fn, &dir_stat) == 0 && S_ISREG(dir_stat.st_mode)) {
1794 amfree(lower_hostname);
1798 amfree(lower_hostname);