Imported Upstream version 3.3.1
[debian/amanda] / recover-src / amrecover.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998, 2000 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 /*
27  * $Id: amrecover.c,v 1.73 2006/07/25 18:27:57 martinea Exp $
28  *
29  * an interactive program for recovering backed-up files
30  */
31
32 #include "amanda.h"
33 #include "stream.h"
34 #include "amfeatures.h"
35 #include "amrecover.h"
36 #include "getfsent.h"
37 #include "dgram.h"
38 #include "util.h"
39 #include "conffile.h"
40 #include "protocol.h"
41 #include "event.h"
42 #include "security.h"
43 #include "conffile.h"
44 #include "getopt.h"
45
46 #define amrecover_debug(i, ...) do {    \
47         if ((i) <= debug_amrecover) {   \
48             dbprintf(__VA_ARGS__);      \
49         }                               \
50 } while (0)
51
52 static struct option long_options[] = {
53     {"version"         , 0, NULL,  1},
54     {NULL, 0, NULL, 0}
55 };
56
57 extern int process_line(char *line);
58 int get_line(void);
59 int grab_reply(int show);
60 void sigint_handler(int signum);
61 int main(int argc, char **argv);
62
63 #define USAGE _("Usage: amrecover [--version] [[-C] <config>] [-s <index-server>] [-t <tape-server>] [-d <tape-device>] [-o <clientconfigoption>]*\n")
64
65 char *server_name = NULL;
66 int server_socket;
67 char *server_line = NULL;
68 char *dump_datestamp = NULL;            /* date we are restoring */
69 char *dump_hostname;                    /* which machine we are restoring */
70 char *disk_name = NULL;                 /* disk we are restoring */
71 dle_t *dump_dle = NULL;
72 char *mount_point = NULL;               /* where disk was mounted */
73 char *disk_path = NULL;                 /* path relative to mount point */
74 char *disk_tpath = NULL;                /* translated path relative to mount point */
75 char dump_date[STR_SIZE];               /* date on which we are restoring */
76 int quit_prog;                          /* set when time to exit parser */
77 char *tape_server_name = NULL;
78 int tape_server_socket;
79 char *tape_device_name = NULL;
80 am_feature_t *our_features = NULL;
81 char *our_features_string = NULL;
82 am_feature_t *indexsrv_features = NULL;
83 am_feature_t *tapesrv_features = NULL;
84 proplist_t proplist = NULL;
85 static char *errstr = NULL;
86 char *authopt;
87 int amindexd_alive = 0;
88
89 static struct {
90     const char *name;
91     security_stream_t *fd;
92 } streams[] = {
93 #define MESGFD  0
94     { "MESG", NULL },
95 };
96 #define NSTREAMS        (int)(sizeof(streams) / sizeof(streams[0]))
97
98 static void amindexd_response(void *, pkt_t *, security_handle_t *);
99 void stop_amindexd(void);
100
101 static char* mesg_buffer = NULL;
102 /* gets a "line" from server and put in server_line */
103 /* server_line is terminated with \0, \r\n is striped */
104 /* returns -1 if error */
105
106 int
107 get_line(void)
108 {
109     ssize_t size;
110     char *newbuf, *s;
111     void *buf;
112
113     if (!mesg_buffer)
114         mesg_buffer = stralloc("");
115  
116     while (!strstr(mesg_buffer,"\r\n")) {
117         buf = NULL;
118         size = security_stream_read_sync(streams[MESGFD].fd, &buf);
119         if(size < 0) {
120             amrecover_debug(1, "amrecover: get_line size < 0 (%zd)\n", size);
121             return -1;
122         }
123         else if(size == 0) {
124             amrecover_debug(1, "amrecover: get_line size == 0 (%zd)\n", size);
125             return -1;
126         }
127         else if (buf == NULL) {
128             amrecover_debug(1, "amrecover: get_line buf == NULL\n");
129             return -1;
130         }
131         amrecover_debug(1, "amrecover: get_line size = %zd\n", size);
132         newbuf = alloc(strlen(mesg_buffer)+size+1);
133         strncpy(newbuf, mesg_buffer, (size_t)(strlen(mesg_buffer) + size));
134         memcpy(newbuf+strlen(mesg_buffer), buf, (size_t)size);
135         newbuf[strlen(mesg_buffer)+size] = '\0';
136         amfree(mesg_buffer);
137         mesg_buffer = newbuf;
138         amfree(buf);
139     }
140
141     s = strstr(mesg_buffer,"\r\n");
142     *s = '\0';
143     newbuf = stralloc(s+2);
144     server_line = newstralloc(server_line, mesg_buffer);
145     amfree(mesg_buffer);
146     mesg_buffer = newbuf;
147     amrecover_debug(1, "server_line: %s\n", server_line);
148     amrecover_debug(1, "get: %s\n", mesg_buffer);
149     return 0;
150 }
151
152
153 /* get reply from server and print to screen */
154 /* handle multi-line reply */
155 /* return -1 if error */
156 /* return code returned by server always occupies first 3 bytes of global
157    variable server_line */
158 /* show == 0: Print the reply if it is an error */
159 /* show == 1: Always print the reply            */
160 int
161 grab_reply(
162     int show)
163 {
164     do {
165         if (get_line() == -1) {
166             return -1;
167         }
168         if (show || server_line[0] == '5') {
169             puts(server_line);
170         }
171     } while (server_line[3] == '-');
172     if(show) fflush(stdout);
173
174     return 0;
175 }
176
177
178 /* get 1 line of reply */
179 /* returns -1 if error, 0 if last (or only) line, 1 if more to follow */
180 int
181 get_reply_line(void)
182 {
183     if (get_line() == -1)
184         return -1;
185     return server_line[3] == '-';
186 }
187
188
189 /* returns pointer to returned line */
190 char *
191 reply_line(void)
192 {
193     return server_line;
194 }
195
196
197
198 /* returns 0 if server returned an error code (ie code starting with 5)
199    and non-zero otherwise */
200 int
201 server_happy(void)
202 {
203     return server_line[0] != '5';
204 }
205
206
207 int
208 send_command(
209     char *      cmd)
210 {
211     /*
212      * NOTE: this routine is called from sigint_handler, so we must be
213      * **very** careful about what we do since there is no way to know
214      * our state at the time the interrupt happened.  For instance,
215      * do not use any stdio or malloc routines here.
216      */
217     char *buffer;
218
219     buffer = alloc(strlen(cmd)+3);
220     strncpy(buffer, cmd, strlen(cmd));
221     buffer[strlen(cmd)] = '\r';
222     buffer[strlen(cmd)+1] = '\n';
223     buffer[strlen(cmd)+2] = '\0';
224
225     g_debug("sending: %s\n", buffer);
226     if(security_stream_write(streams[MESGFD].fd, buffer, strlen(buffer)) < 0) {
227         return -1;
228     }
229     amfree(buffer);
230     return (0);
231 }
232
233
234 /* send a command to the server, get reply and print to screen */
235 int
236 converse(
237     char *      cmd)
238 {
239     if (send_command(cmd) == -1) return -1;
240     if (grab_reply(1) == -1) return -1;
241     return 0;
242 }
243
244
245 /* same as converse() but reply not echoed to stdout */
246 int
247 exchange(
248     char *      cmd)
249 {
250     if (send_command(cmd) == -1) return -1;
251     if (grab_reply(0) == -1) return -1;
252     return 0;
253 }
254
255
256 /* basic interrupt handler for when user presses ^C */
257 /* Bale out, letting server know before doing so */
258 void
259 sigint_handler(
260     int signum)
261 {
262     /*
263      * NOTE: we must be **very** careful about what we do here since there
264      * is no way to know our state at the time the interrupt happened.
265      * For instance, do not use any stdio routines here or in any called
266      * routines.  Also, use _exit() instead of exit() to make sure stdio
267      * buffer flushing is not attempted.
268      */
269     (void)signum;       /* Quiet unused parameter warning */
270
271     if (extract_restore_child_pid != -1)
272         (void)kill(extract_restore_child_pid, SIGKILL);
273     extract_restore_child_pid = -1;
274
275     if(amindexd_alive) 
276         (void)send_command("QUIT");
277
278     _exit(1);
279 }
280
281
282 char *
283 clean_pathname(
284     char *      s)
285 {
286     size_t length;
287     length = strlen(s);
288
289     /* remove "/" at end of path */
290     if(length>1 && s[length-1]=='/')
291         s[length-1]='\0';
292
293     /* change "/." to "/" */
294     if(strcmp(s,"/.")==0)
295         s[1]='\0';
296
297     /* remove "/." at end of path */
298     if(strcmp(&(s[length-2]),"/.")==0)
299         s[length-2]='\0';
300
301     return s;
302 }
303
304
305 void
306 quit(void)
307 {
308     quit_prog = 1;
309     (void)converse("QUIT");
310     stop_amindexd();
311 }
312
313 #ifdef DEFAULT_TAPE_SERVER
314 # define DEFAULT_TAPE_SERVER_FAILOVER (DEFAULT_TAPE_SERVER)
315 #else
316 # define DEFAULT_TAPE_SERVER_FAILOVER (NULL)
317 #endif
318
319 int
320 main(
321     int         argc,
322     char **     argv)
323 {
324     int i;
325     time_t timer;
326     char *lineread = NULL;
327     struct sigaction act, oact;
328     extern char *optarg;
329     extern int optind;
330     char *line = NULL;
331     const security_driver_t *secdrv;
332     char *req = NULL;
333     int response_error;
334     struct tm *tm;
335     config_overrides_t *cfg_ovr;
336     char *starting_hostname = NULL;
337
338     /*
339      * Configure program for internationalization:
340      *   1) Only set the message locale for now.
341      *   2) Set textdomain for all amanda related programs to "amanda"
342      *      We don't want to be forced to support dozens of message catalogs.
343      */  
344     setlocale(LC_MESSAGES, "C");
345     textdomain("amanda"); 
346
347     safe_fd(-1, 0);
348
349     set_pname("amrecover");
350
351     /* Don't die when child closes pipe */
352     signal(SIGPIPE, SIG_IGN);
353
354     dbopen(DBG_SUBDIR_CLIENT);
355
356     /* treat amrecover-specific command line options as the equivalent
357      * -o command-line options to set configuration values */
358     cfg_ovr = new_config_overrides(argc/2);
359
360     /* If the first argument is not an option flag, then we assume
361      * it is a configuration name to match the syntax of the other
362      * Amanda utilities. */
363     if (argc > 1 && argv[1][0] != '-') {
364         add_config_override(cfg_ovr, "conf", argv[1]);
365
366         /* remove that option from the command line */
367         argv[1] = argv[0];
368         argv++; argc--;
369     }
370
371     /* now parse regular command-line '-' options */
372     while ((i = getopt_long(argc, argv, "o:C:s:t:d:Uh:", long_options, NULL)) != EOF) {
373         switch (i) {
374             case 1:
375                 printf("amrecover-%s\n", VERSION);
376                 return(0);
377                 break;
378             case 'C':
379                 add_config_override(cfg_ovr, "conf", optarg);
380                 break;
381
382             case 's':
383                 add_config_override(cfg_ovr, "index_server", optarg);
384                 break;
385
386             case 't':
387                 add_config_override(cfg_ovr, "tape_server", optarg);
388                 break;
389
390             case 'd':
391                 add_config_override(cfg_ovr, "tapedev", optarg);
392                 break;
393
394             case 'o':
395                 add_config_override_opt(cfg_ovr, optarg);
396                 break;
397
398             case 'h':
399                 starting_hostname = g_strdup(optarg);
400                 break;
401
402             case 'U':
403             case '?':
404                 (void)g_printf(USAGE);
405                 return 0;
406         }
407     }
408     if (optind != argc) {
409         (void)g_fprintf(stderr, USAGE);
410         exit(1);
411     }
412
413     /* load the base client configuration */
414     set_config_overrides(cfg_ovr);
415     config_init(CONFIG_INIT_CLIENT, NULL);
416
417     if (config_errors(NULL) >= CFGERR_WARNINGS) {
418         config_print_errors();
419         if (config_errors(NULL) >= CFGERR_ERRORS) {
420             g_critical(_("errors processing config file"));
421         }
422     }
423
424     /* and now try to load the configuration named in that file */
425     config_init(CONFIG_INIT_CLIENT | CONFIG_INIT_EXPLICIT_NAME | CONFIG_INIT_OVERLAY,
426                 getconf_str(CNF_CONF));
427
428     check_running_as(RUNNING_AS_ROOT);
429
430     dbrename(get_config_name(), DBG_SUBDIR_CLIENT);
431
432     our_features = am_init_feature_set();
433     our_features_string = am_feature_to_string(our_features);
434
435     if (!starting_hostname) {
436         starting_hostname = alloc(MAX_HOSTNAME_LENGTH+1);
437         if (gethostname(starting_hostname, MAX_HOSTNAME_LENGTH) != 0) {
438             error(_("cannot determine local host name\n"));
439             /*NOTREACHED*/
440         }
441         starting_hostname[MAX_HOSTNAME_LENGTH] = '\0';
442     }
443
444     server_name = NULL;
445     if (getconf_seen(CNF_INDEX_SERVER) == -2) { /* command line argument */
446         server_name = getconf_str(CNF_INDEX_SERVER);
447     }
448     if (!server_name) {
449         server_name = getenv("AMANDA_SERVER");
450         if (server_name) {
451             g_printf(_("Using index server from environment AMANDA_SERVER (%s)\n"), server_name);
452         }
453     }
454     if (!server_name) {
455         server_name = getconf_str(CNF_INDEX_SERVER);
456     }
457     if (!server_name) {
458         error(_("No index server set"));
459         /*NOTREACHED*/
460     }
461     server_name = stralloc(server_name);
462
463     tape_server_name = NULL;
464     if (getconf_seen(CNF_TAPE_SERVER) == -2) { /* command line argument */
465         tape_server_name = getconf_str(CNF_TAPE_SERVER);
466     }
467     if (!tape_server_name) {
468         tape_server_name = getenv("AMANDA_TAPE_SERVER");
469         if (!tape_server_name) {
470             tape_server_name = getenv("AMANDA_TAPESERVER");
471             if (tape_server_name) {
472                 g_printf(_("Using tape server from environment AMANDA_TAPESERVER (%s)\n"), tape_server_name);
473             }
474         } else {
475             g_printf(_("Using tape server from environment AMANDA_TAPE_SERVER (%s)\n"), tape_server_name);
476         }
477     }
478     if (!tape_server_name) {
479         tape_server_name = getconf_str(CNF_TAPE_SERVER);
480     }
481     if (!tape_server_name) {
482         error(_("No tape server set"));
483         /*NOTREACHED*/
484     }
485     tape_server_name = stralloc(tape_server_name);
486
487     amfree(tape_device_name);
488     tape_device_name = getconf_str(CNF_TAPEDEV);
489     if (!tape_device_name ||
490         strlen(tape_device_name) == 0 ||
491         !getconf_seen(CNF_TAPEDEV)) {
492         tape_device_name = NULL;
493     } else {
494         tape_device_name = stralloc(tape_device_name);
495     }
496
497     authopt = stralloc(getconf_str(CNF_AUTH));
498
499
500     amfree(disk_name);
501     amfree(mount_point);
502     amfree(disk_path);
503     amfree(disk_tpath);
504     dump_date[0] = '\0';
505
506     /* Don't die when child closes pipe */
507     signal(SIGPIPE, SIG_IGN);
508
509     /* set up signal handler */
510     act.sa_handler = sigint_handler;
511     sigemptyset(&act.sa_mask);
512     act.sa_flags = 0;
513     if (sigaction(SIGINT, &act, &oact) != 0) {
514         error(_("error setting signal handler: %s"), strerror(errno));
515         /*NOTREACHED*/
516     }
517
518     proplist = g_hash_table_new_full(g_str_hash, g_str_equal, &g_free, &free_property_t);
519
520     protocol_init();
521
522     /* We assume that amindexd support fe_amindexd_options_features */
523     /*                             and fe_amindexd_options_auth     */
524     /* We should send a noop to really know                         */
525     req = vstrallocf("SERVICE amindexd\n"
526                     "OPTIONS features=%s;auth=%s;\n",
527                     our_features_string, authopt);
528
529     secdrv = security_getdriver(authopt);
530     if (secdrv == NULL) {
531         error(_("no '%s' security driver available for host '%s'"),
532             authopt, server_name);
533         /*NOTREACHED*/
534     }
535
536     protocol_sendreq(server_name, secdrv, generic_client_get_security_conf,
537                      req, STARTUP_TIMEOUT, amindexd_response, &response_error);
538
539     amfree(req);
540     protocol_run();
541
542     g_printf(_("AMRECOVER Version %s. Contacting server on %s ...\n"),
543            VERSION, server_name);
544
545     if(response_error != 0) {
546         g_fprintf(stderr,"%s\n",errstr);
547         exit(1);
548     }
549
550     /* get server's banner */
551     if (grab_reply(1) == -1) {
552         aclose(server_socket);
553         exit(1);
554     }
555     if (!server_happy()) {
556         dbclose();
557         aclose(server_socket);
558         exit(1);
559     }
560
561     /* try to get the features from the server */
562     {
563         char *their_feature_string = NULL;
564
565         indexsrv_features = NULL;
566
567         line = vstrallocf("FEATURES %s", our_features_string);
568         if(exchange(line) == 0) {
569             their_feature_string = stralloc(server_line+13);
570             indexsrv_features = am_string_to_feature(their_feature_string);
571             if (!indexsrv_features)
572                 g_printf(_("Bad feature string from server: %s"), their_feature_string);
573         }
574         if (!indexsrv_features)
575             indexsrv_features = am_set_default_feature_set();
576
577         amfree(their_feature_string);
578         amfree(line);
579     }
580
581     /* set the date of extraction to be today */
582     (void)time(&timer);
583     tm = localtime(&timer);
584     if (tm) 
585         strftime(dump_date, sizeof(dump_date), "%Y-%m-%d", tm);
586     else
587         error(_("BAD DATE"));
588
589     g_printf(_("Setting restore date to today (%s)\n"), dump_date);
590     line = vstrallocf("DATE %s", dump_date);
591     if (converse(line) == -1) {
592         aclose(server_socket);
593         exit(1);
594     }
595     amfree(line);
596
597     line = vstrallocf("SCNF %s", get_config_name());
598     if (converse(line) == -1) {
599         aclose(server_socket);
600         exit(1);
601     }
602     amfree(line);
603
604     if (server_happy()) {
605         /* set host we are restoring to this host by default */
606         amfree(dump_hostname);
607         set_host(starting_hostname);
608         if (dump_hostname)
609             g_printf(_("Use the setdisk command to choose dump disk to recover\n"));
610         else
611             g_printf(_("Use the sethost command to choose a host to recover\n"));
612
613     }
614
615     quit_prog = 0;
616     do {
617         if ((lineread = readline("amrecover> ")) == NULL) {
618             clearerr(stdin);
619             putchar('\n');
620             break;
621         }
622         if (lineread[0] != '\0') 
623         {
624             add_history(lineread);
625             dbprintf(_("user command: '%s'\n"), lineread);
626             process_line(lineread);     /* act on line's content */
627         }
628         amfree(lineread);
629     } while (!quit_prog);
630
631     dbclose();
632
633     aclose(server_socket);
634     return 0;
635 }
636
637 static void
638 amindexd_response(
639     void *datap,
640     pkt_t *pkt,
641     security_handle_t *sech)
642 {
643     int ports[NSTREAMS], *response_error = datap, i;
644     char *p;
645     char *tok;
646     char *extra = NULL;
647
648     assert(response_error != NULL);
649     assert(sech != NULL);
650
651     if (pkt == NULL) {
652         errstr = newvstrallocf(errstr, _("[request failed: %s]"),
653                              security_geterror(sech));
654         *response_error = 1;
655         return;
656     }
657
658     if (pkt->type == P_NAK) {
659 #if defined(PACKET_DEBUG)
660         dbprintf(_("got nak response:\n----\n%s\n----\n\n"), pkt->body);
661 #endif
662
663         tok = strtok(pkt->body, " ");
664         if (tok == NULL || strcmp(tok, "ERROR") != 0)
665             goto bad_nak;
666
667         tok = strtok(NULL, "\n");
668         if (tok != NULL) {
669             errstr = newvstrallocf(errstr, "NAK: %s", tok);
670             *response_error = 1;
671         } else {
672 bad_nak:
673             errstr = newvstrallocf(errstr, _("request NAK"));
674             *response_error = 2;
675         }
676         return;
677     }
678
679     if (pkt->type != P_REP) {
680         errstr = newvstrallocf(errstr, _("received strange packet type %s: %s"),
681                               pkt_type2str(pkt->type), pkt->body);
682         *response_error = 1;
683         return;
684     }
685
686 #if defined(PACKET_DEBUG)
687     g_fprintf(stderr, _("got response:\n----\n%s\n----\n\n"), pkt->body);
688 #endif
689
690     for(i = 0; i < NSTREAMS; i++) {
691         ports[i] = -1;
692         streams[i].fd = NULL;
693     }
694
695     p = pkt->body;
696     while((tok = strtok(p, " \n")) != NULL) {
697         p = NULL;
698
699         /*
700          * Error response packets have "ERROR" followed by the error message
701          * followed by a newline.
702          */
703         if (strcmp(tok, "ERROR") == 0) {
704             tok = strtok(NULL, "\n");
705             if (tok == NULL) {
706                 errstr = newvstrallocf(errstr, _("[bogus error packet]"));
707             } else {
708                 errstr = newvstrallocf(errstr, "%s", tok);
709             }
710             *response_error = 2;
711             return;
712         }
713
714
715         /*
716          * Regular packets have CONNECT followed by three streams
717          */
718         if (strcmp(tok, "CONNECT") == 0) {
719
720             /*
721              * Parse the three stream specifiers out of the packet.
722              */
723             for (i = 0; i < NSTREAMS; i++) {
724                 tok = strtok(NULL, " ");
725                 if (tok == NULL || strcmp(tok, streams[i].name) != 0) {
726                     extra = vstrallocf(
727                            _("CONNECT token is \"%s\": expected \"%s\""),
728                            tok ? tok : _("(null)"), streams[i].name);
729                     goto parse_error;
730                 }
731                 tok = strtok(NULL, " \n");
732                 if (tok == NULL || sscanf(tok, "%d", &ports[i]) != 1) {
733                     extra = vstrallocf(
734                            _("CONNECT %s token is \"%s\" expected a port number"),
735                            streams[i].name, tok ? tok : _("(null)"));
736                     goto parse_error;
737                 }
738             }
739             continue;
740         }
741
742         /*
743          * OPTIONS [options string] '\n'
744          */
745         if (strcmp(tok, "OPTIONS") == 0) {
746             tok = strtok(NULL, "\n");
747             if (tok == NULL) {
748                 extra = vstrallocf(_("OPTIONS token is missing"));
749                 goto parse_error;
750             }
751 #if 0
752             tok_end = tok + strlen(tok);
753             while((p = strchr(tok, ';')) != NULL) {
754                 *p++ = '\0';
755                 if(strncmp_const(tok, "features=") == 0) {
756                     tok += SIZEOF("features=") - 1;
757                     am_release_feature_set(their_features);
758                     if((their_features = am_string_to_feature(tok)) == NULL) {
759                         errstr = newvstrallocf(errstr,
760                                       _("OPTIONS: bad features value: %s"),
761                                       tok);
762                         goto parse_error;
763                     }
764                 }
765                 tok = p;
766             }
767 #endif
768             continue;
769         }
770 #if 0
771         extra = vstrallocf(_("next token is \"%s\": expected \"CONNECT\", \"ERROR\" or \"OPTIONS\""), tok ? tok : _("(null)"));
772         goto parse_error;
773 #endif
774     }
775
776     /*
777      * Connect the streams to their remote ports
778      */
779     for (i = 0; i < NSTREAMS; i++) {
780 /*@i@*/ if (ports[i] == -1)
781             continue;
782         streams[i].fd = security_stream_client(sech, ports[i]);
783         if (streams[i].fd == NULL) {
784             errstr = newvstrallocf(errstr,
785                         _("[could not connect %s stream: %s]"),
786                         streams[i].name, security_geterror(sech));
787             goto connect_error;
788         }
789     }
790     /*
791      * Authenticate the streams
792      */
793     for (i = 0; i < NSTREAMS; i++) {
794         if (streams[i].fd == NULL)
795             continue;
796         if (security_stream_auth(streams[i].fd) < 0) {
797             errstr = newvstrallocf(errstr,
798                 _("[could not authenticate %s stream: %s]"),
799                 streams[i].name, security_stream_geterror(streams[i].fd));
800             goto connect_error;
801         }
802     }
803
804     /*
805      * The MESGFD and DATAFD streams are mandatory.  If we didn't get
806      * them, complain.
807      */
808     if (streams[MESGFD].fd == NULL) {
809         errstr = newvstrallocf(errstr, _("[couldn't open MESG streams]"));
810         goto connect_error;
811     }
812
813     /* everything worked */
814     *response_error = 0;
815     amindexd_alive = 1;
816     return;
817
818 parse_error:
819     errstr = newvstrallocf(errstr,
820                           _("[parse of reply message failed: %s]"),
821                           extra ? extra : _("(no additional information)"));
822     amfree(extra);
823     *response_error = 2;
824     return;
825
826 connect_error:
827     stop_amindexd();
828     *response_error = 1;
829 }
830
831 /*
832  * This is called when everything needs to shut down so event_loop()
833  * will exit.
834  */
835 void
836 stop_amindexd(void)
837 {
838     int i;
839
840     amindexd_alive = 0;
841     for (i = 0; i < NSTREAMS; i++) {
842         if (streams[i].fd != NULL) {
843             security_stream_close(streams[i].fd);
844             streams[i].fd = NULL;
845         }
846     }
847 }
848
849
850 char *
851 translate_octal(
852     char *line)
853 {
854     char *s = line, *s1, *s2;
855     char *p = line;
856     int i;
857
858     if (!translate_mode)
859         return strdup(line);
860
861     while(*s != '\0') {
862         if ((s == line || *(s-1) != '\\') && *s == '\\') {
863             s++;
864             s1 = s+1;
865             s2 = s+2;
866             if (g_ascii_isdigit(*s) && *s1 != '\0' &&
867                 g_ascii_isdigit(*s1) &&
868                 g_ascii_isdigit(*s2)) {
869                 i = ((*s)-'0')*64 + ((*s1)-'0')*8 + ((*s2)-'0');
870                 *p++ = i;
871                 s += 3;
872             } else {
873                 *p++ = *s++;
874             }
875
876         } else {
877             *p++ = *s++;
878         }
879     }
880     *p = '\0';
881
882     return line;
883 }
884