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.2.9 2007/02/07 15:23:45 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"
58 #include "pipespawn.h"
62 typedef struct REMOVE_ITEM
65 struct REMOVE_ITEM *next;
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;
81 static int amindexd_debug = 0;
83 static REMOVE_ITEM *uncompress_remove = NULL;
84 /* uncompressed files to remove */
86 static am_feature_t *our_features = NULL;
87 static am_feature_t *their_features = NULL;
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 **);
93 static size_t reply_buffer_size = 1;
94 static char *reply_buffer = NULL;
95 static char *amandad_auth = NULL;
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,
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 **);
128 dbprintf(("%s: removing index file: %s\n",
129 debug_prefix_time(NULL), remove->filename));
130 unlink(remove->filename);
131 amfree(remove->filename);
133 remove = remove->next;
145 char *filename = NULL;
146 struct stat stat_filename;
159 amwait_t wait_status;
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';
170 /* uncompress the file */
171 result=stat(filename,&stat_filename);
172 if(result==-1 && errno==ENOENT) { /* file does not exist */
174 REMOVE_ITEM *remove_file;
177 * Check that compressed file exists manually.
179 if (stat(filename_gz, &statbuf) < 0) {
180 *emsg = newvstralloc(*emsg, "Compressed file '",
182 "' is inaccessable: ",
185 dbprintf(("%s\n",*emsg));
190 #ifdef UNCOMPRESS_OPT
191 # define PARAM_UNCOMPRESS_OPT UNCOMPRESS_OPT
193 # define PARAM_UNCOMPRESS_OPT skip_argument
199 debugfd = open("/dev/null", O_WRONLY);
203 nullfd = open("/dev/null", O_RDONLY);
204 indexfd = open(filename,O_WRONLY|O_CREAT, 0600);
206 *emsg = newvstralloc(*emsg, "Can't open '",
207 filename, "' for writting: ",
210 dbprintf(("%s\n",*emsg));
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,
223 pipe_stream = fdopen(pipe_from_gzip,"r");
224 if(pipe_stream == NULL) {
225 *emsg = newvstralloc(*emsg, "Can't fdopen pipe from gzip: ",
228 dbprintf(("%s\n",*emsg));
233 /* start the sort process */
234 pid_sort = pipespawn(SORT_PATH, STDIN_PIPE,
235 &pipe_to_sort, &indexfd, &debugfd,
237 if (debugnullfd == 1)
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));
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)));
262 dbprintf(("Uncompres returned negative value: %s",
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)));
274 dbprintf(("Sort returned negative value: %s",
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))) {
286 *emsg = vstralloc("\"", filename, "\" is not a regular file", NULL);
296 /* find all matching entries in a dump listing */
297 /* return -1 if error */
301 DUMP_ITEM * dump_item,
306 char *old_line = NULL;
307 char *filename = NULL;
309 char *dir_slash = NULL;
313 size_t len_dir_slash;
316 if (strcmp(dir, "/") == 0) {
317 dir_slash = stralloc(dir);
319 dir_slash = stralloc2(dir, "/");
322 filename_gz = getindexfname(dump_hostname, disk_name, dump_item->date,
324 if (stat(filename_gz, &statbuf) < 0 && errno == ENOENT) {
326 filename_gz = getoldindexfname(dump_hostname, disk_name,
327 dump_item->date, dump_item->level);
329 if((filename = uncompress_file(filename_gz, emsg)) == NULL) {
336 if((fp = fopen(filename,"r"))==0) {
338 *emsg = stralloc(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(old_line == NULL || strcmp(line, old_line) != 0) {
361 add_dir_list_item(dump_item, line);
363 old_line = stralloc(line);
369 /*@i@*/ amfree(old_line);
375 /* send a 1 line reply to the client */
376 printf_arglist_function1(static void reply, int, n, char *, fmt)
382 reply_buffer = alloc(reply_buffer_size);
385 arglist_start(args, fmt);
386 len = vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
389 if (len > -1 && (size_t)len < reply_buffer_size-1)
392 reply_buffer_size *= 2;
393 amfree(reply_buffer);
394 reply_buffer = alloc(reply_buffer_size);
397 if (fprintf(cmdout,"%03d %s\r\n", n, reply_buffer) < 0)
399 dbprintf(("%s: ! error %d (%s) in printf\n",
400 debug_prefix_time(NULL), errno, strerror(errno)));
401 uncompress_remove = remove_files(uncompress_remove);
404 if (fflush(cmdout) != 0)
406 dbprintf(("%s: ! error %d (%s) in fflush\n",
407 debug_prefix_time(NULL), errno, strerror(errno)));
408 uncompress_remove = remove_files(uncompress_remove);
411 dbprintf(("%s: < %03d %s\n", debug_prefix_time(NULL), n, reply_buffer));
414 /* send one line of a multi-line response */
415 printf_arglist_function1(static void lreply, int, n, char *, fmt)
421 reply_buffer = alloc(reply_buffer_size);
424 arglist_start(args, fmt);
425 len = vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
428 if (len > -1 && (size_t)len < reply_buffer_size-1)
431 reply_buffer_size *= 2;
432 amfree(reply_buffer);
433 reply_buffer = alloc(reply_buffer_size);
436 if (fprintf(cmdout,"%03d-%s\r\n", n, reply_buffer) < 0)
438 dbprintf(("%s: ! error %d (%s) in printf\n",
439 debug_prefix_time(NULL), errno, strerror(errno)));
440 uncompress_remove = remove_files(uncompress_remove);
443 if (fflush(cmdout) != 0)
445 dbprintf(("%s: ! error %d (%s) in fflush\n",
446 debug_prefix_time(NULL), errno, strerror(errno)));
447 uncompress_remove = remove_files(uncompress_remove);
451 dbprintf(("%s: < %03d-%s\n", debug_prefix_time(NULL), n, reply_buffer));
455 /* send one line of a multi-line response */
456 printf_arglist_function1(static void fast_lreply, int, n, char *, fmt)
462 reply_buffer = alloc(reply_buffer_size);
465 arglist_start(args, fmt);
466 len = vsnprintf(reply_buffer, reply_buffer_size, fmt, args);
469 if (len > -1 && (size_t)len < reply_buffer_size-1)
472 reply_buffer_size *= 2;
473 amfree(reply_buffer);
474 reply_buffer = alloc(reply_buffer_size);
477 if (fprintf(cmdout,"%03d-%s\r\n", n, reply_buffer) < 0)
479 dbprintf(("%s: ! error %d (%s) in printf\n",
480 debug_prefix_time(NULL), errno, strerror(errno)));
481 uncompress_remove = remove_files(uncompress_remove);
485 dbprintf(("%s: < %03d-%s\n", debug_prefix_time(NULL), n, reply_buffer));
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 */
497 struct stat dir_stat;
501 if (config_name == NULL) {
502 reply(501, "Must set config before setting host.");
507 /* only let a client restore itself for now unless it is the server */
508 if (strcasecmp(remote_hostname, local_hostname) == 0)
510 if (strcasecmp(remote_hostname, host) != 0)
513 "You don't have the necessary permissions to set dump host to %s.",
519 /* check that the config actually handles that host */
520 ihost = lookup_host(host);
522 reply(501, "Host %s is not in your disklist.", host);
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);
544 struct stat dir_stat;
548 if (config_name == NULL) {
549 reply(501, "Must set config,host before setting disk.");
552 else if (dump_hostname == NULL) {
553 reply(501, "Must set host before setting disk.");
557 /* check that the config actually handles that disk */
558 idisk = lookup_disk(dump_hostname, disk);
560 qdisk = quote_string(disk);
561 reply(501, "Disk %s:%s is not in your disklist.", dump_hostname, qdisk);
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);
589 struct stat dir_stat;
591 /* check that the config actually exists */
592 if (config == NULL) {
593 reply(501, "Must set config first.");
598 conffile = stralloc2(config_dir, CONFFILE_NAME);
599 if (read_conffile(conffile)) {
600 reply(501, "Could not read config file %s!", conffile);
606 conf_diskfile = getconf_str(CNF_DISKFILE);
607 if (*conf_diskfile == '/') {
608 conf_diskfile = stralloc(conf_diskfile);
610 conf_diskfile = stralloc2(config_dir, conf_diskfile);
612 if (read_diskfile(conf_diskfile, &disk_list) < 0) {
613 reply(501, "Could not read disk file %s!", conf_diskfile);
614 amfree(conf_diskfile);
617 amfree(conf_diskfile);
619 conf_tapelist = getconf_str(CNF_TAPELIST);
620 if (*conf_tapelist == '/') {
621 conf_tapelist = stralloc(conf_tapelist);
623 conf_tapelist = stralloc2(config_dir, conf_tapelist);
625 if(read_tapelist(conf_tapelist)) {
626 reply(501, "Could not read tapelist file %s!", conf_tapelist);
627 amfree(conf_tapelist);
630 amfree(conf_tapelist);
632 dbrename(config, DBG_SUBDIR_SERVER);
634 output_find = find_dump(1, &disk_list);
635 sort_find_result("DLKHpB", &output_find);
637 conf_indexdir = getconf_str(CNF_INDEXDIR);
638 if(*conf_indexdir == '/') {
639 conf_indexdir = stralloc(conf_indexdir);
641 conf_indexdir = stralloc2(config_dir, conf_indexdir);
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);
648 amfree(conf_indexdir);
655 build_disk_table(void)
658 char *last_timestamp;
662 find_result_t *find_output;
664 if (config_name == NULL) {
665 reply(590, "Must set config,host,disk before building disk table");
668 else if (dump_hostname == NULL) {
669 reply(590, "Must set host,disk before building disk table");
672 else if (disk_name == NULL) {
673 reply(590, "Must set disk before building disk table");
678 last_timestamp = NULL;
679 last_filenum = (off_t)-1;
682 for(find_output = output_find;
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) {
689 if(strcmp("--", find_output->partnum)){
690 partnum = atoi(find_output->partnum);
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).
699 strcmp(find_output->timestamp, last_timestamp) == 0 &&
700 find_output->level == last_level &&
701 partnum == last_partnum && last_filenum == 0) {
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,
714 (OFF_T_FMT_TYPE)find_output->filenum,
723 disk_history_list(void)
728 if (config_name == NULL) {
729 reply(502, "Must set config,host,disk before listing history");
732 else if (dump_hostname == NULL) {
733 reply(502, "Must set host,disk before listing history");
736 else if (disk_name == NULL) {
737 reply(502, "Must set disk before listing history");
741 lreply(200, " Dump history for config \"%s\" host \"%s\" disk %s",
742 config_name, dump_hostname, qdisk_name);
744 for (item=first_dump(); item!=NULL; item=next_dump(item)){
745 char *tapelist_str = marshal_tapelist(item->tapes, 1);
747 strncpy(date, item->date, 20);
749 if(!am_has_feature(their_features,fe_amrecover_timestamp))
752 if(am_has_feature(their_features, fe_amindexd_marshall_in_DHST)){
753 lreply(201, " %s %d %s", date, item->level, tapelist_str);
756 lreply(201, " %s %d %s " OFF_T_FMT, date, item->level,
757 tapelist_str, (OFF_T_FMT_TYPE)item->file);
759 amfree(tapelist_str);
762 reply(200, "Dump history for config \"%s\" host \"%s\" disk %s",
763 config_name, dump_hostname, qdisk_name);
770 * is the directory dir backed up - dir assumed complete relative to
773 /* opaque version of command */
783 char *filename_gz = NULL;
784 char *filename = NULL;
786 static char *emsg = NULL;
788 if (config_name == NULL || dump_hostname == NULL || disk_name == NULL) {
789 reply(502, "Must set config,host,disk before asking about directories");
792 else if (dump_hostname == NULL) {
793 reply(502, "Must set host,disk before asking about directories");
796 else if (disk_name == NULL) {
797 reply(502, "Must set disk before asking about directories");
800 else if (target_date == NULL) {
801 reply(502, "Must set date before asking about directories");
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)
812 /* no dump for given date */
813 reply(500, "No dumps available on or before date \"%s\"", target_date);
817 if(strcmp(dir, "/") == 0) {
818 ldir = stralloc(dir);
820 ldir = stralloc2(dir, "/");
822 ldir_len = strlen(ldir);
824 /* go back till we hit a level 0 dump */
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);
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));
845 while (fgets(line, STR_SIZE, fp) != NULL) {
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 */
860 last_level = item->level;
863 item=next_dump(item);
864 } while ((item != NULL) && (item->level >= last_level));
865 } while (item != NULL);
869 reply(500, "\"%s\" is an invalid directory", dir);
878 DUMP_ITEM *dump_item;
880 int level, last_level;
881 static char *emsg = NULL;
882 am_feature_e marshall_feature;
885 marshall_feature = fe_amindexd_marshall_in_ORLD;
887 marshall_feature = fe_amindexd_marshall_in_OLSD;
892 if (config_name == NULL) {
893 reply(502, "Must set config,host,disk before listing a directory");
896 else if (dump_hostname == NULL) {
897 reply(502, "Must set host,disk before listing a directory");
900 else if (disk_name == NULL) {
901 reply(502, "Must set disk before listing a directory");
904 else if (target_date == NULL) {
905 reply(502, "Must set date before listing a directory");
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)
914 if (dump_item == NULL)
916 /* no dump for given date */
917 reply(500, "No dumps available on or before date \"%s\"", target_date);
921 /* get data from that dump */
922 if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
923 reply(599, "System error %s", emsg);
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))
932 if (dump_item->level < last_level)
934 last_level = dump_item->level;
935 if (process_ls_dump(dir, dump_item, recursive, &emsg) == -1) {
936 reply(599, "System error %s", emsg);
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) {
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.");
958 opaque_ls_one(dir_item, marshall_feature, recursive);
963 reply(200, " Opaque list of %s", dir);
971 am_feature_e marshall_feature,
978 if (am_has_feature(their_features, marshall_feature)) {
979 tapelist_str = marshal_tapelist(dir_item->dump->tapes, 1);
981 tapelist_str = dir_item->dump->tapes->label;
984 strncpy(date, dir_item->dump->date, 20);
986 if(!am_has_feature(their_features,fe_amrecover_timestamp))
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",
996 dir_item->dump->level,
998 (OFF_T_FMT_TYPE)dir_item->dump->file,
1003 fast_lreply(201, " %s %d %s %s",
1004 date, dir_item->dump->level,
1005 tapelist_str, qpath);
1008 if(am_has_feature(their_features, marshall_feature)) {
1009 amfree(tapelist_str);
1014 * returns the value of changer or tapedev from the amanda.conf file if set,
1015 * otherwise reports an error
1023 /* check state okay to do this */
1024 if (config_name == NULL) {
1025 reply(501, "Must set config before asking about tapedev.");
1029 /* use amrecover_changer if possible */
1030 if ((result = getconf_str(CNF_AMRECOVER_CHANGER)) != NULL &&
1032 dbprintf(("%s: tapedev_is amrecover_changer: %s\n",
1033 debug_prefix_time(NULL), result));
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));
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));
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.");
1061 /* returns YES if dumps for disk are compressed, NO if not */
1063 are_dumps_compressed(void)
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.");
1072 else if (dump_hostname == NULL) {
1073 reply(501, "Must set host,disk name before asking about dumps.");
1076 else if (disk_name == NULL) {
1077 reply(501, "Must set disk name before asking about dumps.");
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)) {
1089 if (diskp == NULL) {
1090 reply(501, "Couldn't find host/disk in disk file.");
1094 /* send data to caller */
1095 if (diskp->compress == COMP_NONE)
1108 char *line = NULL, *part = NULL;
1111 char *cmd_undo, cmd_undo_ch;
1113 struct sockaddr_in his_addr;
1114 struct hostent *his_name;
1118 int user_validated = 0;
1119 char *errstr = NULL;
1120 char *pgm = "amindexd"; /* in case argv[0] is not set */
1122 safe_fd(DATA_FD_OFFSET, 2);
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.
1131 if (argc >= 1 && argv != NULL && argv[0] != NULL) {
1132 if((pgm = strrchr(argv[0], '/')) != NULL) {
1141 /* Don't die when child closes pipe */
1142 signal(SIGPIPE, SIG_IGN);
1146 /* we'd rather not run as root */
1148 if(geteuid() == 0) {
1149 if(client_uid == (uid_t) -1) {
1150 error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
1155 initgroups(CLIENT_LOGIN, client_gid);
1161 #endif /* FORCE_USERID */
1163 dbopen(DBG_SUBDIR_SERVER);
1164 dbprintf(("%s: version %s\n", get_pname(), version()));
1167 error("argv == NULL\n");
1170 if (! (argc >= 1 && argv[0] != NULL)) {
1171 dbprintf(("%s: WARNING: argv[0] not defined: check inetd.conf\n",
1172 debug_prefix_time(NULL)));
1187 if(argc > 0 && strcmp(*argv, "-t") == 0) {
1193 if(argc > 0 && strcmp(*argv, "amandad") == 0) {
1198 amandad_auth = *argv;
1209 config_name = stralloc(*argv);
1210 config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
1215 if(gethostname(local_hostname, SIZEOF(local_hostname)-1) == -1) {
1216 error("gethostname: %s", strerror(errno));
1219 local_hostname[SIZEOF(local_hostname)-1] = '\0';
1221 /* now trim domain off name */
1224 while(ch && ch != '.') ch = *s++;
1228 if(from_amandad == 0) {
1229 if(amindexd_debug) {
1231 * Fake the remote address as the local address enough to get
1232 * through the security check.
1234 his_name = gethostbyname(local_hostname);
1235 if(his_name == NULL) {
1236 error("gethostbyname(%s) failed\n", local_hostname);
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);
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));
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));
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));
1265 fp = s = stralloc(his_name->h_name);
1267 while(ch && ch != '.') ch = *s++;
1269 remote_hostname = newstralloc(remote_hostname, fp);
1276 cmdfdout = DATA_FD_OFFSET + 0;
1277 cmdfdin = DATA_FD_OFFSET + 1;
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) {
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';
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);
1303 /* send the REP packet */
1304 printf("CONNECT MESG %d\n", DATA_FD_OFFSET);
1311 cmdout = fdopen(cmdfdout, "a");
1313 error("amindexd: Can't fdopen(cmdfdout): %s", strerror(errno));
1317 cmdin = fdopen(cmdfdin, "r");
1319 error("amindexd: Can't fdopen(cmdfdin): %s", strerror(errno));
1324 /* clear these so we can detect when the have not been set by the client */
1325 amfree(dump_hostname);
1328 amfree(target_date);
1330 our_features = am_init_feature_set();
1331 their_features = am_set_default_feature_set();
1333 if (config_name != NULL && is_config_valid(config_name) != -1) {
1337 reply(220, "%s AMANDA index server (%s) ready.", local_hostname,
1340 user_validated = from_amandad;
1342 /* a real simple parser since there are only a few commands */
1345 /* get a line from the client */
1347 if((part = agets(cmdin)) == NULL) {
1349 dbprintf(("%s: ? read error: %s\n",
1350 debug_prefix_time(NULL), strerror(errno)));
1352 dbprintf(("%s: ? unexpected EOF\n",
1353 debug_prefix_time(NULL)));
1356 dbprintf(("%s: ? unprocessed input:\n",
1357 debug_prefix_time(NULL)));
1358 dbprintf(("-----\n"));
1359 dbprintf(("? %s\n", line));
1360 dbprintf(("-----\n"));
1364 uncompress_remove = remove_files(uncompress_remove);
1366 return 1; /* they hung up? */
1368 strappend(line, part); /* Macro: line can be null */
1371 if(amindexd_debug) {
1372 break; /* we have a whole line */
1374 if((len = strlen(line)) > 0 && line[len-1] == '\r') {
1375 line[len-1] = '\0'; /* zap the '\r' */
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.
1383 strappend(line, "\n");
1386 dbprintf(("%s: > %s\n", debug_prefix_time(NULL), line));
1393 skip_whitespace(s, ch);
1395 reply(500, "Command not recognised/incorrect: %s", line);
1401 skip_non_whitespace(s, ch);
1402 cmd_undo = s-1; /* for error message */
1403 cmd_undo_ch = *cmd_undo;
1406 skip_whitespace(s, ch); /* find the argument */
1409 skip_quoted_string(s, ch);
1410 arg = unquote_string(arg);
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");
1423 if (!user_validated) { /* don't tell client the reason, just log it to debug log */
1424 reply(500, "Access not allowed");
1426 dbprintf(("%s: %s\n", debug_prefix_time(NULL), errstr));
1431 if (strcmp(cmd, "QUIT") == 0) {
1434 } else if (strcmp(cmd, "HOST") == 0 && arg) {
1435 /* set host we are restoring */
1437 if (is_dump_host_valid(arg) != -1)
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 */
1445 } else if (strcmp(cmd, "LISTHOST") == 0) {
1451 if (config_name == NULL) {
1452 reply(501, "Must set config before listhost");
1455 lreply(200, " List hosts for config %s", config_name);
1456 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
1458 for (diskdup = disk_list.head; diskdup!=disk; diskdup = diskdup->next) {
1459 if(strcmp(diskdup->host->hostname, disk->host->hostname) == 0) {
1465 fast_lreply(201, " %s", disk->host->hostname);
1470 reply(200, " List hosts for config %s", config_name);
1473 reply(200, "No hosts for config %s", config_name);
1477 } else if (strcmp(cmd, "DISK") == 0 && arg) {
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);
1487 } else if (strcmp(cmd, "LISTDISK") == 0) {
1492 if (config_name == NULL) {
1493 reply(501, "Must set config, host before listdisk");
1495 else if (dump_hostname == NULL) {
1496 reply(501, "Must set host before listdisk");
1499 lreply(200, " List of disk for device %s on host %s", arg,
1501 for (disk = disk_list.head; disk!=NULL; disk = disk->next) {
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);
1513 reply(200, "List of disk for device %s on host %s", arg,
1517 reply(200, "No disk for device %s on host %s", arg,
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);
1532 reply(200, "List of disk for host %s", dump_hostname);
1535 reply(200, "No disk for host %s", dump_hostname);
1539 } else if (strcmp(cmd, "SCNF") == 0 && arg) {
1541 amfree(config_name);
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);
1551 amfree(config_name);
1555 } else if (strcmp(cmd, "FEATURES") == 0 && arg) {
1556 char *our_feature_string = NULL;
1557 char *their_feature_string = NULL;
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);
1569 } else if (strcmp(cmd, "DATE") == 0 && arg) {
1571 target_date = newstralloc(target_date, arg);
1572 reply(200, "Working date set to %s.", target_date);
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);
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) {
1586 } else if (strcmp(cmd, "DCMP") == 0) {
1587 (void)are_dumps_compressed();
1589 *cmd_undo = cmd_undo_ch; /* restore the command line */
1590 reply(500, "Command not recognised/incorrect: %s", cmd);
1596 uncompress_remove = remove_files(uncompress_remove);
1597 free_find_result(&output_find);
1598 reply(200, "Good bye.");
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;
1613 strncpy(date, datestamp, 8);
1615 numdate = atoi(date);
1616 year = numdate / 10000;
1617 month = (numdate / 100) % 100;
1618 day = numdate % 100;
1620 if(strlen(datestamp) <= 8) {
1621 snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d",
1625 strncpy(atime, &(datestamp[8]), 6);
1627 numtime = atoi(atime);
1628 hours = numtime / 10000;
1629 minutes = (numtime / 100) % 100;
1630 seconds = numtime % 100;
1632 snprintf(nice, SIZEOF(nice), "%4d-%02d-%02d-%02d-%02d-%02d",
1633 year, month, day, hours, minutes, seconds);
1644 return strncmp(date1, date2, strlen(date2));
1651 char *s = line, *s1, *s2;
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');
1667 } else if (*s == '\\') { /* we remove one / */
1669 } else { /* we keep the / */