a3a27f8057a12a1c5c3b87e64d2a58b1882cf014
[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.58 2006/03/14 13:12:01 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
40 #include "changer.h"
41 #include "tapeio.h"
42 #include "conffile.h"
43 #include "logfile.h"
44 #include "amfeatures.h"
45 #include "stream.h"
46
47 #define TIMEOUT 30
48
49 static char *pgm = "amidxtaped";        /* in case argv[0] is not set */
50
51 extern char *rst_conf_logdir;
52 extern char *rst_conf_logfile;
53 extern char *config_dir;
54
55 static int get_lock = 0;
56
57 static am_feature_t *our_features = NULL;
58 static am_feature_t *their_features = NULL;
59
60 static char *get_client_line P((void));
61 static char *get_client_line_fd P((int));
62 static void check_security_buffer P((char*));
63
64 /* exit routine */
65 static int parent_pid = -1;
66 static void cleanup P((void));
67
68 /* get a line from client - line terminated by \r\n */
69 static char *
70 get_client_line()
71 {
72     static char *line = NULL;
73     char *part = NULL;
74     int len;
75
76     amfree(line);
77     while(1) {
78         if((part = agets(stdin)) == NULL) {
79             if(errno != 0) {
80                 dbprintf(("%s: read error: %s\n",
81                           debug_prefix_time(NULL), strerror(errno)));
82             } else {
83                 dbprintf(("%s: EOF reached\n", debug_prefix_time(NULL)));
84             }
85             if(line) {
86                 dbprintf(("%s: unprocessed input:\n", debug_prefix_time(NULL)));
87                 dbprintf(("-----\n"));
88                 dbprintf(("%s\n", line));
89                 dbprintf(("-----\n"));
90             }
91             amfree(line);
92             amfree(part);
93             dbclose();
94             exit(1);
95             /* NOTREACHED */
96         }
97         if(line) {
98             strappend(line, part);
99             amfree(part);
100         } else {
101             line = part;
102             part = NULL;
103         }
104         if((len = strlen(line)) > 0 && line[len-1] == '\r') {
105             line[len-1] = '\0';         /* zap the '\r' */
106             break;
107         }
108         /*
109          * Hmmm.  We got a "line" from agets(), which means it saw
110          * a '\n' (or EOF, etc), but there was not a '\r' before it.
111          * Put a '\n' back in the buffer and loop for more.
112          */
113         strappend(line, "\n");
114     }
115     dbprintf(("%s: > %s\n", debug_prefix_time(NULL), line));
116     return line;
117 }
118
119 /* get a line from client - line terminated by \r\n */
120 static char *
121 get_client_line_fd(fd)
122 int fd;
123 {
124     static char *line = NULL;
125     static int line_size = 0;
126     char *s = line;
127     int len = 0;
128     char c;
129     int nb;
130
131     if(line == NULL) { /* first time only, allocate initial buffer */
132         s = line = malloc(128);
133         line_size = 128;
134     }
135     while(1) {
136         nb = read(fd, &c, 1);
137         if (nb <= 0) {
138             /* EOF or error */
139             if ((nb <= 0) && ((errno == EINTR) || (errno == EAGAIN))) {
140                 /* Keep looping if failure is temporary */
141                 continue;
142             }
143             dbprintf(("%s: Control pipe read error - %s\n",
144                 pgm, strerror(errno)));
145             break;
146         }
147
148         if(len >= line_size-1) { /* increase buffer size */
149             line_size *= 2;
150             line = realloc(line, line_size);
151             if (line == NULL) {
152                 error("Memory reallocation failure");
153                 /* NOTREACHED */
154             }
155             s = &line[len];
156         }
157         *s = c;
158         if(c == '\n') {
159             if(len > 0 && *(s-1) == '\r') { /* remove '\r' */
160                 s--;
161                 len--;
162             }
163             *s = '\0';
164             return line;
165         }
166         s++;
167         len++;
168     }
169     line[len] = '\0';
170     return line;
171 }
172
173
174 void check_security_buffer(buffer)
175      char *buffer;
176 {
177     socklen_t i;
178     struct sockaddr_in addr;
179     char *s, *fp, ch;
180     char *errstr = NULL;
181     
182      i = sizeof (addr);
183      if (getpeername(0, (struct sockaddr *)&addr, &i) == -1)
184          error("getpeername: %s", strerror(errno));
185      if (addr.sin_family != AF_INET || ntohs(addr.sin_port) == 20) {
186        error("connection rejected from %s family %d port %d",
187              inet_ntoa(addr.sin_addr), addr.sin_family, htons(addr.sin_port));
188      }
189      
190      /* do the security thing */
191      s = buffer;
192      ch = *s++;
193
194      skip_whitespace(s, ch);
195      if (ch == '\0') {
196              error("cannot parse SECURITY line");
197      }
198      fp = s-1;
199      skip_non_whitespace(s, ch);
200      s[-1] = '\0';
201      if (strcmp(fp, "SECURITY") != 0) {
202          error("cannot parse SECURITY line");
203      }
204      skip_whitespace(s, ch);
205      if (!check_security(&addr, s-1, 0, &errstr)) {
206          error("security check failed: %s", errstr);
207      }
208 }
209
210 int main(argc, argv)
211 int argc;
212 char **argv;
213 {
214     char *buf = NULL;
215     int data_sock = -1, data_port = -1;
216     socklen_t socklen;
217     struct sockaddr_in addr;
218     match_list_t *match_list;
219     tapelist_t *tapes = NULL;
220     char *their_feature_string = NULL;
221     rst_flags_t *rst_flags;
222     int use_changer = 0;
223     FILE *prompt_stream = NULL;
224     int re_end = 0;
225     char *re_config = NULL;
226     char *conf_tapetype;
227     tapetype_t *tape;
228
229     safe_fd(-1, 0);
230     safe_cd();
231
232     /* Don't die when child closes pipe */
233     signal(SIGPIPE, SIG_IGN);
234
235     rst_flags = new_rst_flags();
236     rst_flags->mask_splits = 1; /* for older clients */
237     rst_flags->amidxtaped = 1;
238     our_features = am_init_feature_set();
239     their_features = am_set_default_feature_set();
240
241     /*
242      * When called via inetd, it is not uncommon to forget to put the
243      * argv[0] value on the config line.  On some systems (e.g. Solaris)
244      * this causes argv and/or argv[0] to be NULL, so we have to be
245      * careful getting our name.
246      */
247     if (argc >= 1 && argv != NULL && argv[0] != NULL) {
248         if((pgm = strrchr(argv[0], '/')) != NULL) {
249             pgm++;
250         } else {
251             pgm = argv[0];
252         }
253     }
254
255     set_pname(pgm);
256
257 #ifdef FORCE_USERID
258
259     /* we'd rather not run as root */
260
261     if(geteuid() == 0) {
262         if(client_uid == (uid_t) -1) {
263             error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
264         }
265
266         initgroups(CLIENT_LOGIN, client_gid);
267         setgid(client_gid);
268         setuid(client_uid);
269     }
270
271 #endif  /* FORCE_USERID */
272
273     /* initialize */
274     /* close stderr first so that debug file becomes it - amrestore
275        chats to stderr, which we don't want going to client */
276     /* if no debug file, ship to bit bucket */
277     (void)close(STDERR_FILENO);
278     dbopen();
279     startclock();
280     dbprintf(("%s: version %s\n", pgm, version()));
281 #ifdef DEBUG_CODE
282     if(dbfd() != -1 && dbfd() != STDERR_FILENO)
283     {
284         if(dup2(dbfd(),STDERR_FILENO) != STDERR_FILENO)
285         {
286             perror("amidxtaped can't redirect stderr to the debug file");
287             dbprintf(("%s: can't redirect stderr to the debug file\n",
288                       debug_prefix_time(NULL)));
289             return 1;
290         }
291     }
292 #else
293     if ((i = open("/dev/null", O_WRONLY)) == -1 ||
294         (i != STDERR_FILENO &&
295          (dup2(i, STDERR_FILENO) != STDERR_FILENO ||
296           close(i) != 0))) {
297         perror("amidxtaped can't redirect stderr");
298         return 1;
299     }
300 #endif
301
302     if (! (argc >= 1 && argv != NULL && argv[0] != NULL)) {
303         dbprintf(("%s: WARNING: argv[0] not defined: check inetd.conf\n",
304                   debug_prefix_time(NULL)));
305     }
306
307     socklen = sizeof (addr);
308     if (getpeername(0, (struct sockaddr *)&addr, &socklen) == -1)
309         error("getpeername: %s", strerror(errno));
310     if (addr.sin_family != AF_INET || ntohs(addr.sin_port) == 20) {
311         error("connection rejected from %s family %d port %d",
312               inet_ntoa(addr.sin_addr), addr.sin_family, htons(addr.sin_port));
313     }
314
315     /* do the security thing */
316     amfree(buf);
317     buf = stralloc(get_client_line());
318     check_security_buffer(buf);
319
320     /* get the number of arguments */
321     match_list = alloc(sizeof(match_list_t));
322     match_list->next = NULL;
323     match_list->hostname = "";
324     match_list->datestamp = "";
325     match_list->level = "";
326     match_list->diskname = "";
327
328    do {
329         amfree(buf);
330         buf = stralloc(get_client_line());
331         if(strncmp(buf, "LABEL=", 6) == 0) {
332             tapes = unmarshal_tapelist_str(buf+6);
333         }
334         else if(strncmp(buf, "FSF=", 4) == 0) {
335             rst_flags->fsf = atoi(buf + 4);
336         }
337         else if(strncmp(buf, "HEADER", 6) == 0) {
338             rst_flags->headers = 1;
339         }
340         else if(strncmp(buf, "FEATURES=", 9) == 0) {
341             char *our_feature_string = NULL;
342             their_feature_string = stralloc(buf+9);
343             am_release_feature_set(their_features);
344             their_features = am_string_to_feature(their_feature_string);
345             amfree(their_feature_string);
346             our_feature_string = am_feature_to_string(our_features);
347             printf("%s", our_feature_string);
348             fflush(stdout);
349             amfree(our_feature_string);
350         }
351         else if(strncmp(buf, "DEVICE=", 7) == 0) {
352             rst_flags->alt_tapedev= stralloc(buf+7);
353         }
354         else if(strncmp(buf, "HOST=", 5) == 0) {
355             match_list->hostname = stralloc(buf+5);
356         }
357         else if(strncmp(buf, "DISK=", 5) == 0) {
358             match_list->diskname = stralloc(buf+5);
359         }
360         else if(strncmp(buf, "DATESTAMP=", 10) == 0) {
361             match_list->datestamp = stralloc(buf+10);
362         }
363         else if(strncmp(buf, "END", 3) == 0) {
364             re_end = 1;
365         }
366         else if(strncmp(buf, "CONFIG=", 7) == 0) {
367             re_config = stralloc(buf+7);
368         }
369         else if(buf[0] != '\0' && buf[0] >= '0' && buf[0] <= '9') {
370 /* XXX does nothing?     amrestore_nargs = atoi(buf); */
371             re_end = 1;
372         }
373         else {
374         }
375     } while (re_end == 0);
376     amfree(buf);
377
378     if(!tapes && rst_flags->alt_tapedev){
379         dbprintf(("%s: Looks like we're restoring from a holding file...\n", debug_prefix_time(NULL)));
380         tapes = unmarshal_tapelist_str(rst_flags->alt_tapedev);
381         tapes->isafile = 1;
382         amfree(rst_flags->alt_tapedev);
383         rst_flags->alt_tapedev = NULL;
384     }
385
386     if(re_config) {
387         char *conffile;
388         config_dir = vstralloc(CONFIG_DIR, "/", re_config, "/", NULL);
389         conffile = stralloc2(config_dir, CONFFILE_NAME);
390         if (read_conffile(conffile)) {
391             dbprintf(("%s: config '%s' not found\n",
392                       debug_prefix_time(NULL), re_config));
393             amfree(re_config);
394             re_config = NULL;
395         }
396         amfree(conffile);
397     }
398
399     if(tapes && 
400        (!rst_flags->alt_tapedev  ||
401         (re_config && ( strcmp(rst_flags->alt_tapedev,
402                                getconf_str(CNF_AMRECOVER_CHANGER)) == 0 ||
403                         strcmp(rst_flags->alt_tapedev,
404                                getconf_str(CNF_TPCHANGER)) == 0 ) ) ) ) {
405         /* We need certain options, if restoring from more than one tape */
406         if(tapes->next && !am_has_feature(their_features, fe_recover_splits)) {
407             error("%s: Client must support split dumps to restore requested data.",  get_pname());
408             /* NOTREACHED */
409         }
410         dbprintf(("%s: Restoring from changer, checking labels\n", get_pname()));
411         rst_flags->check_labels = 1;
412         use_changer = 1;
413     }
414
415     /* If we'll be stepping on the tape server's devices, lock them. */
416     if(re_config  &&
417        (use_changer || (rst_flags->alt_tapedev &&
418                         strcmp(rst_flags->alt_tapedev,
419                                getconf_str(CNF_TAPEDEV)) == 0) ) ) {
420         dbprintf(("%s: Locking devices\n", get_pname()));
421         parent_pid = getpid();
422         atexit(cleanup);
423         get_lock = lock_logfile();
424     }
425
426     /* Init the tape changer */
427     if(tapes && use_changer && changer_init() == 0) {
428         dbprintf(("%s: No changer available\n", debug_prefix_time(NULL)));
429     }
430
431     /* Read the default block size from the tape type */
432     if(re_config && (conf_tapetype = getconf_str(CNF_TAPETYPE)) != NULL) {
433         tape = lookup_tapetype(conf_tapetype);
434         rst_flags->blocksize = tape->blocksize * 1024;
435     }
436
437     if(rst_flags->fsf && re_config && 
438        getconf_int(CNF_AMRECOVER_DO_FSF) == 0) {
439         rst_flags->fsf = 0;
440     }
441
442     if(re_config && getconf_int(CNF_AMRECOVER_CHECK_LABEL) == 0) {
443         rst_flags->check_labels = 0;
444     }
445
446     /* establish a distinct data connection for dumpfile data */
447     if(am_has_feature(their_features, fe_recover_splits)){
448         int data_fd;
449         char *buf;
450         
451         dbprintf(("%s: Client understands split dumpfiles\n", get_pname()));
452         
453         if((data_sock = stream_server(&data_port, STREAM_BUFSIZE, -1)) < 0){
454             error("%s: could not create data socket: %s", get_pname(),
455                   strerror(errno));
456         }
457         dbprintf(("%s: Local port %d set aside for data\n", get_pname(),
458                   data_port));
459         
460         printf("CONNECT %d\n", data_port); /* tell client where to connect */
461         fflush(stdout);
462         
463         if((data_fd = stream_accept(data_sock, TIMEOUT, -1, -1)) < 0){
464             error("stream_accept failed for client data connection: %s\n",
465                   strerror(errno));
466         }
467
468         buf = get_client_line_fd(data_fd);
469
470         check_security_buffer(buf);
471         rst_flags->pipe_to_fd = data_fd;
472         prompt_stream = stdout;
473     }
474     else {
475         rst_flags->pipe_to_fd = fileno(stdout);
476         prompt_stream = stderr;
477     }
478     dbprintf(("%s: Sending output to file descriptor %d\n",
479               get_pname(), rst_flags->pipe_to_fd));
480     
481     
482     /* make sure our restore flags aren't crazy */
483     if(check_rst_flags(rst_flags) == -1){
484         if(rst_flags->pipe_to_fd != -1) aclose(rst_flags->pipe_to_fd);
485         exit(1);
486     }
487     
488     /* actual restoration */
489     search_tapes(prompt_stream, use_changer, tapes, match_list, rst_flags,
490                  their_features);
491     dbprintf(("%s: Restoration finished\n", debug_prefix_time(NULL)));
492     
493     /* cleanup */
494     if(rst_flags->pipe_to_fd != -1) aclose(rst_flags->pipe_to_fd);
495     free_tapelist(tapes);
496     
497     am_release_feature_set(their_features);
498
499     amfree(rst_flags->alt_tapedev);
500     amfree(rst_flags);
501     amfree(match_list->hostname);
502     amfree(match_list->diskname);
503     amfree(match_list->datestamp);
504     amfree(match_list);
505     amfree(config_dir);
506     amfree(re_config);
507     dbclose();
508     return 0;
509 }
510
511 static void
512 cleanup(void)
513 {
514     if(parent_pid == getpid()) {
515         if(get_lock) unlink(rst_conf_logfile);
516     }
517 }
518