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