78abffd899205913c3c9f093df66928b2c36e4bc
[debian/amanda] / restore-src / amidxtaped.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 /* $Id: amidxtaped.c,v 1.73 2006/07/25 19:06:46 martinea Exp $
27  *
28  * This daemon extracts a dump image off a tape for amrecover and
29  * returns it over the network. It basically, reads a number of
30  * arguments from stdin (it is invoked via inet), one per line,
31  * preceeded by the number of them, and forms them into an argv
32  * structure, then execs amrestore
33  */
34
35 #include "amanda.h"
36 #include "version.h"
37 #include "clock.h"
38 #include "restore.h"
39 #include "cmdline.h"
40
41 #include "changer.h"
42 #include "conffile.h"
43 #include "logfile.h"
44 #include "amfeatures.h"
45 #include "stream.h"
46 #include "amandad.h"
47 #include "server_util.h"
48
49 #define amidxtaped_debug(i,x) do {      \
50         if ((i) <= debug_amidxtaped) {  \
51             dbprintf(x);                \
52         }                               \
53 } while (0)
54
55 #define TIMEOUT 30
56
57 static char *pgm = "amidxtaped";        /* in case argv[0] is not set */
58
59 extern char *rst_conf_logfile;
60
61 static int get_lock = 0;
62 static int from_amandad;
63
64 static am_feature_t *our_features = NULL;
65 static am_feature_t *their_features = NULL;
66 static g_option_t *g_options = NULL;
67 static int ctlfdin, ctlfdout, datafdout;
68 static char *amandad_auth = NULL;
69 static FILE *cmdin, *cmdout;
70
71 static char *get_client_line(FILE *in);
72 static void check_security_buffer(char *);
73 static char *get_client_line_fd(int);
74
75 /* exit routine */
76 static pid_t parent_pid = -1;
77 static void cleanup(void);
78
79 int main(int argc, char **argv);
80
81 /* get a line from client - line terminated by \r\n */
82 static char *
83 get_client_line(FILE *in)
84 {
85     static char *line = NULL;
86     char *part = NULL;
87     size_t len;
88
89     amfree(line);
90     while(1) {
91         if((part = agets(in)) == NULL) {
92             if(errno != 0) {
93                 dbprintf(_("read error: %s\n"), strerror(errno));
94             } else {
95                 dbprintf(_("EOF reached\n"));
96             }
97             if(line) {
98                 dbprintf(_("s: unprocessed input:\n"));
99                 dbprintf("-----\n");
100                 dbprintf("%s\n", line);
101                 dbprintf("-----\n");
102             }
103             amfree(line);
104             amfree(part);
105             dbclose();
106             exit(1);
107             /*NOTREACHED*/
108         }
109         if(line) {
110             strappend(line, part);
111             amfree(part);
112         } else {
113             line = part;
114             part = NULL;
115         }
116         if((len = strlen(line)) > 0 && line[len-1] == '\r') {
117             line[len-1] = '\0';         /* zap the '\r' */
118             break;
119         }
120         /*
121          * Hmmm.  We got a "line" from agets(), which means it saw
122          * a '\n' (or EOF, etc), but there was not a '\r' before it.
123          * Put a '\n' back in the buffer and loop for more.
124          */
125         strappend(line, "\n");
126     }
127     dbprintf("> %s\n", line);
128     return line;
129 }
130
131 /* get a line from client - line terminated by \r\n */
132 static char *
133 get_client_line_fd(
134     int         fd)
135 {
136     static char *line = NULL;
137     static size_t line_size = 0;
138     char *s = line;
139     size_t len = 0;
140     char c;
141     ssize_t nb;
142
143     if(line == NULL) { /* first time only, allocate initial buffer */
144         s = line = alloc(128);
145         line_size = 128;
146     }
147     while(1) {
148         nb = read(fd, &c, 1);
149         if (nb <= 0) {
150             /* EOF or error */
151             if ((nb <= 0) && ((errno == EINTR) || (errno == EAGAIN))) {
152                 /* Keep looping if failure is temporary */
153                 continue;
154             }
155             dbprintf(_("%s: Control pipe read error - %s\n"),
156                       pgm, strerror(errno));
157             break;
158         }
159
160         if(len >= line_size-1) { /* increase buffer size */
161             line_size *= 2;
162             line = realloc(line, line_size);
163             if (line == NULL) {
164                 error(_("Memory reallocation failure"));
165                 /*NOTREACHED*/
166             }
167             s = &line[len];
168         }
169         *s = c;
170         if(c == '\n') {
171             if(len > 0 && *(s-1) == '\r') { /* remove '\r' */
172                 s--;
173                 len--;
174             }
175             *s = '\0';
176             return line;
177         }
178         s++;
179         len++;
180     }
181     line[len] = '\0';
182     return line;
183 }
184
185
186 void
187 check_security_buffer(
188     char *      buffer)
189 {
190     socklen_t_equiv i;
191     struct sockaddr_in addr;
192     char *s, *fp, ch;
193     char *errstr = NULL;
194
195     dbprintf(_("check_security_buffer(buffer='%s')\n"), buffer);
196
197     i = SIZEOF(addr);
198     if (getpeername(0, (struct sockaddr *)&addr, &i) == -1) {
199         error(_("getpeername: %s"), strerror(errno));
200         /*NOTREACHED*/
201     }
202     if ((addr.sin_family != (sa_family_t)AF_INET)
203                 || (ntohs(addr.sin_port) == 20)) {
204         error(_("connection rejected from %s family %d port %d"),
205              inet_ntoa(addr.sin_addr), addr.sin_family, htons(addr.sin_port));
206         /*NOTREACHED*/
207     }
208
209     /* do the security thing */
210     s = buffer;
211     ch = *s++;
212
213     skip_whitespace(s, ch);
214     if (ch == '\0') {
215         error(_("cannot parse SECURITY line"));
216         /*NOTREACHED*/
217     }
218     fp = s-1;
219     skip_non_whitespace(s, ch);
220     s[-1] = '\0';
221     if (strcmp(fp, "SECURITY") != 0) {
222         error(_("cannot parse SECURITY line"));
223         /*NOTREACHED*/
224     }
225     skip_whitespace(s, ch);
226     if (!check_security((sockaddr_union *)&addr, s-1, 0, &errstr)) {
227         error(_("security check failed: %s"), errstr);
228         /*NOTREACHED*/
229     }
230 }
231
232 int
233 main(
234     int         argc,
235     char **     argv)
236 {
237     char *buf = NULL;
238     int data_sock = -1;
239     in_port_t data_port = (in_port_t)-1;
240     socklen_t_equiv socklen;
241     struct sockaddr_in addr;
242     GSList *dumpspecs;
243     tapelist_t *tapes = NULL;
244     char *their_feature_string = NULL;
245     rst_flags_t *rst_flags;
246     int use_changer = 0;
247     int re_end;
248     char *re_config = NULL;
249     char *conf_tapetype;
250     tapetype_t *tape;
251     char *line;
252     char *tapedev;
253     dumpspec_t *ds;
254
255     /*
256      * Configure program for internationalization:
257      *   1) Only set the message locale for now.
258      *   2) Set textdomain for all amanda related programs to "amanda"
259      *      We don't want to be forced to support dozens of message catalogs.
260      */  
261     setlocale(LC_MESSAGES, "C");
262     textdomain("amanda"); 
263
264     safe_fd(DATA_FD_OFFSET, 4);
265     safe_cd();
266
267     /* Don't die when child closes pipe */
268     signal(SIGPIPE, SIG_IGN);
269
270     rst_flags = new_rst_flags();
271     rst_flags->mask_splits = 1; /* for older clients */
272     rst_flags->amidxtaped = 1;
273     our_features = am_init_feature_set();
274     their_features = am_set_default_feature_set();
275
276     /*
277      * When called via inetd, it is not uncommon to forget to put the
278      * argv[0] value on the config line.  On some systems (e.g. Solaris)
279      * this causes argv and/or argv[0] to be NULL, so we have to be
280      * careful getting our name.
281      */
282     if (argc >= 1 && argv != NULL && argv[0] != NULL) {
283         if((pgm = strrchr(argv[0], '/')) != NULL) {
284             pgm++;
285         } else {
286             pgm = argv[0];
287         }
288     }
289
290     set_pname(pgm);
291
292     if(argv && argv[1] && strcmp(argv[1], "amandad") == 0) {
293         from_amandad = 1;
294         if(argv[2])
295             amandad_auth = argv[2];
296     }
297     else {
298         from_amandad = 0;
299         safe_fd(-1, 0);
300     }
301
302     /* initialize */
303     /* close stderr first so that debug file becomes it - amrestore
304        chats to stderr, which we don't want going to client */
305     /* if no debug file, ship to bit bucket */
306     (void)close(STDERR_FILENO);
307     dbopen(DBG_SUBDIR_SERVER);
308     startclock();
309     dbprintf(_("%s: version %s\n"), pgm, version());
310     debug_dup_stderr_to_debug();
311
312     if (! (argc >= 1 && argv != NULL && argv[0] != NULL)) {
313         dbprintf(_("WARNING: argv[0] not defined: check inetd.conf\n"));
314     }
315
316     if(from_amandad == 0) {
317         socklen = SIZEOF(addr);
318         if (getpeername(0, (struct sockaddr *)&addr, &socklen) == -1) {
319             error(_("getpeername: %s"), strerror(errno));
320             /*NOTREACHED*/
321         }
322         if ((addr.sin_family != (sa_family_t)AF_INET)
323                 || (ntohs(addr.sin_port) == 20)) {
324             error(_("connection rejected from %s family %d port %d"),
325                   inet_ntoa(addr.sin_addr), addr.sin_family,
326                   htons(addr.sin_port));
327             /*NOTREACHED*/
328         }
329
330         /* do the security thing */
331         amfree(buf);
332         fflush(stdout);
333         cmdout = stdout;
334         cmdin  = stdin;
335         buf = stralloc(get_client_line(cmdin));
336         check_security_buffer(buf);
337     }
338     else {
339         ctlfdout  = DATA_FD_OFFSET + 0;
340         ctlfdin   = DATA_FD_OFFSET + 1;
341         datafdout = DATA_FD_OFFSET + 2;
342         close(DATA_FD_OFFSET +3);
343
344         /* read the REQ packet */
345         for(; (line = agets(stdin)) != NULL; free(line)) {
346             if(strncmp_const(line, "OPTIONS ") == 0) {
347                 if (g_options)
348                     error(_("ERROR recover program sent multiple OPTIONS"));
349                 g_options = parse_g_options(line+8, 1);
350                 if(!g_options->hostname) {
351                     g_options->hostname = alloc(MAX_HOSTNAME_LENGTH+1);
352                     gethostname(g_options->hostname, MAX_HOSTNAME_LENGTH);
353                     g_options->hostname[MAX_HOSTNAME_LENGTH] = '\0';
354                 }
355             }
356         }
357         amfree(line);
358
359         if(amandad_auth && g_options->auth) {
360             if(strcasecmp(amandad_auth, g_options->auth) != 0) {
361                 g_printf(_("ERROR recover program ask for auth=%s while amidxtaped is configured for '%s'\n"),
362                        g_options->auth, amandad_auth);
363                 error(_("ERROR recover program ask for auth=%s while amidxtaped is configured for '%s'"),
364                       g_options->auth, amandad_auth);
365                 /*NOTREACHED*/
366             }
367         }
368         /* send the REP packet */
369         g_printf("CONNECT CTL %d DATA %d\n", DATA_FD_OFFSET, DATA_FD_OFFSET+1);
370         g_printf("\n");
371         fflush(stdout);
372         fclose(stdin);
373         fclose(stdout);
374         cmdout = fdopen(ctlfdout, "a");
375         if (!cmdout) {
376             error(_("amidxtaped: Can't fdopen(ctlfdout): %s"), strerror(errno));
377             /*NOTREACHED*/
378         }
379         cmdin = fdopen(ctlfdin, "r");
380         if (!cmdin) {
381             error(_("amidxtaped: Can't fdopen(ctlfdin): %s"), strerror(errno));
382             /*NOTREACHED*/
383         }
384     }
385
386     ds = dumpspec_new(NULL, NULL, NULL, NULL);
387     for (re_end = 0; re_end == 0; ) {
388         char *s, ch;
389         amfree(buf);
390         buf = stralloc(get_client_line(cmdin));
391         s = buf;
392         if(strncmp_const_skip(buf, "LABEL=", s, ch) == 0) {
393             tapes = unmarshal_tapelist_str(s);
394         }
395         else if(strncmp_const_skip(buf, "FSF=", s, ch) == 0) {
396             rst_flags->fsf = OFF_T_ATOI(s);
397         }
398         else if(strncmp_const_skip(buf, "HEADER", s, ch) == 0) {
399             rst_flags->headers = 1;
400         }
401         else if(strncmp_const_skip(buf, "FEATURES=", s, ch) == 0) {
402             char *our_feature_string = NULL;
403             their_feature_string = stralloc(s);
404             am_release_feature_set(their_features);
405             their_features = am_string_to_feature(their_feature_string);
406             amfree(their_feature_string);
407             our_feature_string = am_feature_to_string(our_features);
408             if(from_amandad == 1) 
409                 g_fprintf(cmdout,"FEATURES=%s\r\n", our_feature_string);
410             else
411                 g_fprintf(cmdout,"%s", our_feature_string);
412             fflush(cmdout);
413             amfree(our_feature_string);
414         }
415         else if(strncmp_const_skip(buf, "DEVICE=", s, ch) == 0) {
416             rst_flags->alt_tapedev= stralloc(s);
417         }
418         else if(strncmp_const_skip(buf, "HOST=", s, ch) == 0) {
419             if (ds->host) {
420                 dbprintf(_("WARNING: HOST appeared twice in client request.\n"));
421                 amfree(ds->host);
422             }
423             ds->host = stralloc(s);
424         }
425         else if(strncmp_const_skip(buf, "DISK=", s, ch) == 0) {
426             if (ds->disk) {
427                 dbprintf(_("WARNING: DISK appeared twice in client request.\n"));
428                 amfree(ds->disk);
429             }
430             ds->disk = stralloc(s);
431         }
432         else if(strncmp_const_skip(buf, "DATESTAMP=", s, ch) == 0) {
433             if (ds->datestamp) {
434                 dbprintf(_("WARNING: DATESTAMP appeared twice in client request.\n"));
435                 amfree(ds->datestamp);
436             }
437             ds->datestamp = stralloc(s);
438         }
439         else if(strncmp_const(buf, "END") == 0) {
440             re_end = 1;
441         }
442         else if(strncmp_const_skip(buf, "CONFIG=", s, ch) == 0) {
443             re_config = stralloc(s);
444             if(strlen(re_config) == 0)
445                 amfree(re_config);
446         }
447         else if(buf[0] != '\0' && buf[0] >= '0' && buf[0] <= '9') {
448             re_end = 1;
449         }
450     }
451     amfree(buf);
452
453     if(re_config) {
454         config_init(CONFIG_INIT_EXPLICIT_NAME, re_config);
455         dbrename(re_config, DBG_SUBDIR_SERVER);
456     } else {
457         config_init(0, NULL);
458     }
459
460     if (config_errors(NULL) >= CFGERR_ERRORS) {
461         g_critical(_("errors processing config file"));
462     }
463
464     check_running_as(RUNNING_AS_DUMPUSER_PREFERRED);
465
466     if(tapes &&
467        (!rst_flags->alt_tapedev  ||
468         (re_config && ( strcmp(rst_flags->alt_tapedev,
469                                getconf_str(CNF_AMRECOVER_CHANGER)) == 0 ||
470                         strcmp(rst_flags->alt_tapedev,
471                                getconf_str(CNF_TPCHANGER)) == 0 ) ) ) ) {
472         /* We need certain options, if restoring from more than one tape */
473         if(tapes->next && !am_has_feature(their_features, fe_recover_splits)) {
474             error(_("Client must support split dumps to restore requested data."));
475             /*NOTREACHED*/
476         }
477         dbprintf(_("Restoring from changer, checking labels\n"));
478         rst_flags->check_labels = 1;
479         use_changer = 1;
480     }
481
482     /* build the dumpspec list from our single dumpspec */
483     dumpspecs = g_slist_append(NULL, (gpointer)ds);
484     ds = NULL;
485
486     if(!tapes && rst_flags->alt_tapedev){
487         dbprintf(_("Looks like we're restoring from a holding file...\n"));
488         tapes = unmarshal_tapelist_str(rst_flags->alt_tapedev);
489         tapes->isafile = 1;
490         amfree(rst_flags->alt_tapedev);
491         rst_flags->alt_tapedev = NULL;
492         use_changer = FALSE;
493     } 
494
495     tapedev = getconf_str(CNF_TAPEDEV);
496     /* If we'll be stepping on the tape server's devices, lock them. */
497     if(re_config &&
498        (use_changer || (rst_flags->alt_tapedev && tapedev &&
499                         strcmp(rst_flags->alt_tapedev, tapedev) == 0) ) ) {
500         dbprintf(_("Locking devices\n"));
501         parent_pid = getpid();
502         atexit(cleanup);
503         get_lock = lock_logfile();
504     }
505     if (get_lock)
506         log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
507
508     /* Init the tape changer */
509     if(tapes && use_changer && changer_init() == 0) {
510         dbprintf(_("No changer available\n"));
511     }
512
513     /* Read the default block size from the tape type */
514     if(re_config && (conf_tapetype = getconf_str(CNF_TAPETYPE)) != NULL) {
515         tape = lookup_tapetype(conf_tapetype);
516         rst_flags->blocksize = tapetype_get_blocksize(tape) * 1024;
517     }
518
519     if(rst_flags->fsf && re_config &&
520        getconf_boolean(CNF_AMRECOVER_DO_FSF) == 0) {
521         rst_flags->fsf = (off_t)0;
522     }
523
524     if (!use_changer && re_config &&
525         getconf_boolean(CNF_AMRECOVER_CHECK_LABEL) == 0) {
526         rst_flags->check_labels = 0;
527     }
528
529     /* establish a distinct data connection for dumpfile data */
530     if(am_has_feature(their_features, fe_recover_splits)) {
531         if(from_amandad == 1) {
532             rst_flags->pipe_to_fd = datafdout;
533         }
534         else {
535             int data_fd;
536             char *buf;
537
538             dbprintf(_("Client understands split dumpfiles\n"));
539
540             if((data_sock = stream_server(AF_INET, &data_port, STREAM_BUFSIZE, 
541                  STREAM_BUFSIZE, 0)) < 0){
542                 error(_("could not create data socket: %s"), strerror(errno));
543                 /*NOTREACHED*/
544             }
545             dbprintf(_("Local port %d set aside for data\n"), data_port);
546
547             /* tell client where to connect */
548             g_printf(_("CONNECT %hu\n"), (unsigned short)data_port);
549             fflush(stdout);
550
551             if((data_fd = stream_accept(data_sock, TIMEOUT, STREAM_BUFSIZE, 
552                  STREAM_BUFSIZE)) < 0){
553                 error(_("stream_accept failed for client data connection: %s\n"),
554                       strerror(errno));
555                 /*NOTREACHED*/
556             }
557
558             buf = get_client_line_fd(data_fd);
559
560             check_security_buffer(buf);
561             rst_flags->pipe_to_fd = data_fd;
562         }
563     }
564     else {
565         rst_flags->pipe_to_fd = fileno(stdout);
566         cmdout = stderr;
567     }
568     dbprintf(_("Sending output to file descriptor %d\n"), rst_flags->pipe_to_fd);
569
570
571     tapedev = getconf_str(CNF_TAPEDEV);
572     if(get_lock == 0 &&
573        re_config && 
574        (use_changer || (rst_flags->alt_tapedev && tapedev &&
575                         strcmp(rst_flags->alt_tapedev, tapedev) == 0) ) ) {
576         char *process_name = get_master_process(rst_conf_logfile);
577         send_message(cmdout, rst_flags, their_features,
578                      _("%s exists: %s is already running, "
579                      "or you must run amcleanup"), 
580                      rst_conf_logfile, process_name);
581         error(_("%s exists: %s is already running, "
582               "or you must run amcleanup"),
583               rst_conf_logfile, process_name);
584     }
585
586     /* make sure our restore flags aren't crazy */
587     if (check_rst_flags(rst_flags) == -1) {
588         if (rst_flags->pipe_to_fd != -1)
589             aclose(rst_flags->pipe_to_fd);
590         send_message(cmdout, rst_flags, their_features,
591                      _("restore flags are crazy"));
592         exit(1);
593     }
594
595     /* actual restoration */
596     search_tapes(cmdout, cmdin, use_changer, tapes, dumpspecs, rst_flags,
597                  their_features);
598     dbprintf(_("Restoration finished\n"));
599
600     /* cleanup */
601     if(rst_flags->pipe_to_fd != -1) aclose(rst_flags->pipe_to_fd);
602     free_tapelist(tapes);
603
604     am_release_feature_set(their_features);
605
606     amfree(rst_flags->alt_tapedev);
607     amfree(rst_flags);
608     dumpspec_list_free(dumpspecs);
609     amfree(re_config);
610     dbclose();
611     return 0;
612 }
613
614 static void
615 cleanup(void)
616 {
617     if (parent_pid == getpid()) {
618         if (get_lock) {
619             log_add(L_INFO, "pid-done %ld\n", (long)getpid());
620             unlink(rst_conf_logfile);
621         }
622     }
623 }