2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998, 2000 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: amrecover.c,v 1.73.2.2 2007/01/24 18:33:30 martinea Exp $
29 * an interactive program for recovering backed-up files
35 #include "amfeatures.h"
36 #include "amrecover.h"
40 #include "clientconf.h"
45 extern int process_line(char *line);
47 int grab_reply(int show);
48 void sigint_handler(int signum);
49 int main(int argc, char **argv);
51 #define USAGE "Usage: amrecover [[-C] <config>] [-s <index-server>] [-t <tape-server>] [-d <tape-device>] [-o <clientconfigoption>]*\n"
54 char *server_name = NULL;
56 char *server_line = NULL;
57 char *dump_datestamp = NULL; /* date we are restoring */
58 char *dump_hostname; /* which machine we are restoring */
59 char *disk_name = NULL; /* disk we are restoring */
60 char *mount_point = NULL; /* where disk was mounted */
61 char *disk_path = NULL; /* path relative to mount point */
62 char dump_date[STR_SIZE]; /* date on which we are restoring */
63 int quit_prog; /* set when time to exit parser */
64 char *tape_server_name = NULL;
65 int tape_server_socket;
66 char *tape_device_name = NULL;
67 am_feature_t *our_features = NULL;
68 char *our_features_string = NULL;
69 am_feature_t *indexsrv_features = NULL;
70 am_feature_t *tapesrv_features = NULL;
71 static char *errstr = NULL;
73 int amindexd_alive = 0;
77 security_stream_t *fd;
82 #define NSTREAMS (int)(sizeof(streams) / sizeof(streams[0]))
84 static void amindexd_response(void *, pkt_t *, security_handle_t *);
85 void stop_amindexd(void);
86 char *amindexd_client_get_security_conf(char *, void *);
88 static char* mesg_buffer = NULL;
89 /* gets a "line" from server and put in server_line */
90 /* server_line is terminated with \0, \r\n is striped */
91 /* returns -1 if error */
101 mesg_buffer = stralloc("");
103 while (!strstr(mesg_buffer,"\r\n")) {
104 size = security_stream_read_sync(streams[MESGFD].fd, &buf);
111 newbuf = alloc(strlen(mesg_buffer)+size+1);
112 strncpy(newbuf, mesg_buffer, (size_t)(strlen(mesg_buffer) + size));
113 memcpy(newbuf+strlen(mesg_buffer), buf, (size_t)size);
114 newbuf[strlen(mesg_buffer)+size] = '\0';
116 mesg_buffer = newbuf;
119 s = strstr(mesg_buffer,"\r\n");
121 newbuf = stralloc(s+2);
122 server_line = newstralloc(server_line, mesg_buffer);
124 mesg_buffer = newbuf;
129 /* get reply from server and print to screen */
130 /* handle multi-line reply */
131 /* return -1 if error */
132 /* return code returned by server always occupies first 3 bytes of global
133 variable server_line */
139 if (get_line() == -1) {
142 if(show) puts(server_line);
143 } while (server_line[3] == '-');
144 if(show) fflush(stdout);
150 /* get 1 line of reply */
151 /* returns -1 if error, 0 if last (or only) line, 1 if more to follow */
155 if (get_line() == -1)
157 return server_line[3] == '-';
161 /* returns pointer to returned line */
170 /* returns 0 if server returned an error code (ie code starting with 5)
171 and non-zero otherwise */
175 return server_line[0] != '5';
184 * NOTE: this routine is called from sigint_handler, so we must be
185 * **very** careful about what we do since there is no way to know
186 * our state at the time the interrupt happened. For instance,
187 * do not use any stdio or malloc routines here.
191 buffer = alloc(strlen(cmd)+3);
192 strncpy(buffer, cmd, strlen(cmd));
193 buffer[strlen(cmd)] = '\r';
194 buffer[strlen(cmd)+1] = '\n';
195 buffer[strlen(cmd)+2] = '\0';
197 if(security_stream_write(streams[MESGFD].fd, buffer, strlen(buffer)) < 0) {
205 /* send a command to the server, get reply and print to screen */
210 if (send_command(cmd) == -1) return -1;
211 if (grab_reply(1) == -1) return -1;
216 /* same as converse() but reply not echoed to stdout */
221 if (send_command(cmd) == -1) return -1;
222 if (grab_reply(0) == -1) return -1;
227 /* basic interrupt handler for when user presses ^C */
228 /* Bale out, letting server know before doing so */
234 * NOTE: we must be **very** careful about what we do here since there
235 * is no way to know our state at the time the interrupt happened.
236 * For instance, do not use any stdio routines here or in any called
237 * routines. Also, use _exit() instead of exit() to make sure stdio
238 * buffer flushing is not attempted.
240 (void)signum; /* Quiet unused parameter warning */
242 if (extract_restore_child_pid != -1)
243 (void)kill(extract_restore_child_pid, SIGKILL);
244 extract_restore_child_pid = -1;
247 (void)send_command("QUIT");
260 /* remove "/" at end of path */
261 if(length>1 && s[length-1]=='/')
264 /* change "/." to "/" */
265 if(strcmp(s,"/.")==0)
268 /* remove "/." at end of path */
269 if(strcmp(&(s[length-2]),"/.")==0)
278 (void)converse("QUIT");
282 char *localhost = NULL;
284 #ifdef DEFAULT_TAPE_SERVER
285 # define DEFAULT_TAPE_SERVER_FAILOVER (DEFAULT_TAPE_SERVER)
287 # define DEFAULT_TAPE_SERVER_FAILOVER (NULL)
297 char *lineread = NULL;
298 struct sigaction act, oact;
303 const security_driver_t *secdrv;
312 set_pname("amrecover");
314 /* Don't die when child closes pipe */
315 signal(SIGPIPE, SIG_IGN);
317 dbopen(DBG_SUBDIR_CLIENT);
319 #ifndef IGNORE_UID_CHECK
320 if (geteuid() != 0) {
321 erroutput_type |= ERR_SYSLOG;
322 error("amrecover must be run by root");
327 localhost = alloc(MAX_HOSTNAME_LENGTH+1);
328 if (gethostname(localhost, MAX_HOSTNAME_LENGTH) != 0) {
329 error("cannot determine local host name\n");
332 localhost[MAX_HOSTNAME_LENGTH] = '\0';
334 parse_client_conf(argc, argv, &new_argc, &new_argv);
336 if (new_argc > 1 && new_argv[1][0] != '-') {
338 * If the first argument is not an option flag, then we assume
339 * it is a configuration name to match the syntax of the other
344 new_argv1 = (char **) alloc((size_t)((new_argc + 1 + 1) * sizeof(*new_argv1)));
345 new_argv1[0] = new_argv[0];
347 for (i = 1; i < new_argc; i++) {
348 new_argv1[i + 1] = new_argv[i];
350 new_argv1[i + 1] = NULL;
353 new_argv = new_argv1;
355 while ((i = getopt(new_argc, new_argv, "C:s:t:d:U")) != EOF) {
358 add_client_conf(CLN_CONF, optarg);
362 add_client_conf(CLN_INDEX_SERVER, optarg);
366 add_client_conf(CLN_TAPE_SERVER, optarg);
370 add_client_conf(CLN_TAPEDEV, optarg);
379 if (optind != new_argc) {
380 (void)fprintf(stderr, USAGE);
384 our_features = am_init_feature_set();
385 our_features_string = am_feature_to_string(our_features);
387 conffile = vstralloc(CONFIG_DIR, "/", "amanda-client.conf", NULL);
388 if (read_clientconf(conffile) > 0) {
389 error("error reading conffile: %s", conffile);
394 config = stralloc(client_getconf_str(CLN_CONF));
396 conffile = vstralloc(CONFIG_DIR, "/", config, "/", "amanda-client.conf",
398 if (read_clientconf(conffile) > 0) {
399 error("error reading conffile: %s", conffile);
404 dbrename(config, DBG_SUBDIR_CLIENT);
406 report_bad_client_arg();
409 if (client_getconf_seen(CLN_INDEX_SERVER) == -2) { /* command line argument */
410 server_name = client_getconf_str(CLN_INDEX_SERVER);
413 server_name = getenv("AMANDA_SERVER");
415 printf("Using index server from environment AMANDA_SERVER (%s)\n", server_name);
419 server_name = client_getconf_str(CLN_INDEX_SERVER);
422 error("No index server set");
425 server_name = stralloc(server_name);
427 tape_server_name = NULL;
428 if (client_getconf_seen(CLN_TAPE_SERVER) == -2) { /* command line argument */
429 tape_server_name = client_getconf_str(CLN_TAPE_SERVER);
431 if (!tape_server_name) {
432 tape_server_name = getenv("AMANDA_TAPE_SERVER");
433 if (!tape_server_name) {
434 tape_server_name = getenv("AMANDA_TAPESERVER");
435 if (tape_server_name) {
436 printf("Using tape server from environment AMANDA_TAPESERVER (%s)\n", tape_server_name);
439 printf("Using tape server from environment AMANDA_TAPE_SERVER (%s)\n", tape_server_name);
442 if (!tape_server_name) {
443 tape_server_name = client_getconf_str(CLN_TAPE_SERVER);
445 if (!tape_server_name) {
446 error("No tape server set");
449 tape_server_name = stralloc(tape_server_name);
451 amfree(tape_device_name);
452 tape_device_name = client_getconf_str(CLN_TAPEDEV);
453 if (tape_device_name)
454 tape_device_name = stralloc(tape_device_name);
456 authopt = stralloc(client_getconf_str(CLN_AUTH));
464 /* Don't die when child closes pipe */
465 signal(SIGPIPE, SIG_IGN);
467 /* set up signal handler */
468 act.sa_handler = sigint_handler;
469 sigemptyset(&act.sa_mask);
471 if (sigaction(SIGINT, &act, &oact) != 0) {
472 error("error setting signal handler: %s", strerror(errno));
478 /* We assume that amindexd support fe_amindexd_options_features */
479 /* and fe_amindexd_options_auth */
480 /* We should send a noop to really know */
481 req = vstralloc("SERVICE amindexd\n",
482 "OPTIONS ", "features=", our_features_string, ";",
483 "auth=", authopt, ";",
486 secdrv = security_getdriver(authopt);
487 if (secdrv == NULL) {
488 error("no '%s' security driver available for host '%s'",
489 authopt, server_name);
493 protocol_sendreq(server_name, secdrv, amindexd_client_get_security_conf,
494 req, STARTUP_TIMEOUT, amindexd_response, &response_error);
499 printf("AMRECOVER Version %s. Contacting server on %s ...\n",
500 version(), server_name);
502 if(response_error != 0) {
503 fprintf(stderr,"%s\n",errstr);
509 * We may need root privilege again later for a reserved port to
510 * the tape server, so we will drop down now but might have to
517 /* get server's banner */
518 if (grab_reply(1) == -1) {
519 aclose(server_socket);
522 if (!server_happy()) {
524 aclose(server_socket);
528 /* try to get the features from the server */
530 char *their_feature_string = NULL;
532 line = stralloc2("FEATURES ", our_features_string);
533 if(exchange(line) == 0) {
534 their_feature_string = stralloc(server_line+13);
535 indexsrv_features = am_string_to_feature(their_feature_string);
538 indexsrv_features = am_set_default_feature_set();
540 amfree(their_feature_string);
544 /* set the date of extraction to be today */
546 tm = localtime(&timer);
548 strftime(dump_date, sizeof(dump_date), "%Y-%m-%d", tm);
552 printf("Setting restore date to today (%s)\n", dump_date);
553 line = stralloc2("DATE ", dump_date);
554 if (converse(line) == -1) {
555 aclose(server_socket);
560 line = stralloc2("SCNF ", config);
561 if (converse(line) == -1) {
562 aclose(server_socket);
567 if (server_happy()) {
568 /* set host we are restoring to this host by default */
569 amfree(dump_hostname);
572 printf("Use the setdisk command to choose dump disk to recover\n");
574 printf("Use the sethost command to choose a host to recover\n");
580 if ((lineread = readline("amrecover> ")) == NULL) {
585 if (lineread[0] != '\0')
587 add_history(lineread);
588 process_line(lineread); /* act on line's content */
591 } while (!quit_prog);
595 aclose(server_socket);
603 security_handle_t *sech)
605 int ports[NSTREAMS], *response_error = datap, i;
610 assert(response_error != NULL);
611 assert(sech != NULL);
614 errstr = newvstralloc(errstr, "[request failed: ",
615 security_geterror(sech), "]", NULL);
620 if (pkt->type == P_NAK) {
621 #if defined(PACKET_DEBUG)
622 fprintf(stderr, "got nak response:\n----\n%s\n----\n\n", pkt->body);
625 tok = strtok(pkt->body, " ");
626 if (tok == NULL || strcmp(tok, "ERROR") != 0)
629 tok = strtok(NULL, "\n");
631 errstr = newvstralloc(errstr, "NAK: ", tok, NULL);
635 errstr = newstralloc(errstr, "request NAK");
641 if (pkt->type != P_REP) {
642 errstr = newvstralloc(errstr, "received strange packet type ",
643 pkt_type2str(pkt->type), ": ", pkt->body, NULL);
648 #if defined(PACKET_DEBUG)
649 fprintf(stderr, "got response:\n----\n%s\n----\n\n", pkt->body);
652 for(i = 0; i < NSTREAMS; i++) {
654 streams[i].fd = NULL;
658 while((tok = strtok(p, " \n")) != NULL) {
662 * Error response packets have "ERROR" followed by the error message
663 * followed by a newline.
665 if (strcmp(tok, "ERROR") == 0) {
666 tok = strtok(NULL, "\n");
668 tok = "[bogus error packet]";
669 errstr = newstralloc(errstr, tok);
676 * Regular packets have CONNECT followed by three streams
678 if (strcmp(tok, "CONNECT") == 0) {
681 * Parse the three stream specifiers out of the packet.
683 for (i = 0; i < NSTREAMS; i++) {
684 tok = strtok(NULL, " ");
685 if (tok == NULL || strcmp(tok, streams[i].name) != 0) {
686 extra = vstralloc("CONNECT token is \"",
687 tok ? tok : "(null)",
694 tok = strtok(NULL, " \n");
695 if (tok == NULL || sscanf(tok, "%d", &ports[i]) != 1) {
696 extra = vstralloc("CONNECT ",
699 tok ? tok : "(null)",
700 "\": expected a port number",
709 * OPTIONS [options string] '\n'
711 if (strcmp(tok, "OPTIONS") == 0) {
712 tok = strtok(NULL, "\n");
714 extra = stralloc("OPTIONS token is missing");
718 tok_end = tok + strlen(tok);
719 while((p = strchr(tok, ';')) != NULL) {
721 #define sc "features="
722 if(strncmp(tok, sc, sizeof(sc)-1) == 0) {
723 tok += sizeof(sc) - 1;
725 am_release_feature_set(their_features);
726 if((their_features = am_string_to_feature(tok)) == NULL) {
727 errstr = newvstralloc(errstr,
728 "OPTIONS: bad features value: ",
740 extra = vstralloc("next token is \"",
741 tok ? tok : "(null)",
742 "\": expected \"CONNECT\", \"ERROR\" or \"OPTIONS\"",
749 * Connect the streams to their remote ports
751 for (i = 0; i < NSTREAMS; i++) {
752 /*@i@*/ if (ports[i] == -1)
754 streams[i].fd = security_stream_client(sech, ports[i]);
755 if (streams[i].fd == NULL) {
756 errstr = newvstralloc(errstr,
757 "[could not connect ", streams[i].name, " stream: ",
758 security_geterror(sech), "]", NULL);
763 * Authenticate the streams
765 for (i = 0; i < NSTREAMS; i++) {
766 if (streams[i].fd == NULL)
768 if (security_stream_auth(streams[i].fd) < 0) {
769 errstr = newvstralloc(errstr,
770 "[could not authenticate ", streams[i].name, " stream: ",
771 security_stream_geterror(streams[i].fd), "]", NULL);
777 * The MESGFD and DATAFD streams are mandatory. If we didn't get
780 if (streams[MESGFD].fd == NULL) {
781 errstr = newstralloc(errstr, "[couldn't open MESG streams]");
785 /* everything worked */
791 errstr = newvstralloc(errstr,
792 "[parse of reply message failed: ",
793 extra ? extra : "(no additional information)",
806 * This is called when everything needs to shut down so event_loop()
815 for (i = 0; i < NSTREAMS; i++) {
816 if (streams[i].fd != NULL) {
817 security_stream_close(streams[i].fd);
818 streams[i].fd = NULL;
824 amindexd_client_get_security_conf(
828 (void)arg; /* Quiet unused parameter warning */
830 if(!string || !*string)
833 if(strcmp(string, "auth")==0) {
834 return(client_getconf_str(CLN_AUTH));
836 else if(strcmp(string, "ssh_keys")==0) {
837 return(client_getconf_str(CLN_SSH_KEYS));